/kernel/branches/kolibri-process/network/ARP.inc |
---|
0,0 → 1,676 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; ARP.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: 3386 $ |
ARP_NO_ENTRY = 0 |
ARP_VALID_MAPPING = 1 |
ARP_AWAITING_RESPONSE = 2 |
ARP_RESPONSE_TIMEOUT = 3 |
ARP_REQUEST_TTL = 31 ; 20 s |
ARP_ENTRY_TTL = 937 ; 600 s |
ARP_STATIC_ENTRY = -1 |
ARP_REQ_OPCODE = 0x0100 ; request |
ARP_REP_OPCODE = 0x0200 ; reply |
ARP_TABLE_SIZE = 20 ; Size of table |
struct ARP_entry |
IP dd ? |
MAC dp ? |
Status dw ? |
TTL dw ? |
ends |
struct ARP_header |
HardwareType dw ? |
ProtocolType dw ? |
HardwareSize db ? |
ProtocolSize db ? |
Opcode dw ? |
SenderMAC dp ? |
SenderIP dd ? |
TargetMAC dp ? |
TargetIP dd ? |
ends |
uglobal |
align 4 |
ARP_table rb NET_DEVICES_MAX*(ARP_TABLE_SIZE * sizeof.ARP_entry) |
ARP_entries_num rd NET_DEVICES_MAX |
ARP_PACKETS_TX rd NET_DEVICES_MAX |
ARP_PACKETS_RX rd NET_DEVICES_MAX |
ARP_CONFLICTS rd NET_DEVICES_MAX |
endg |
;----------------------------------------------------------------- |
; |
; ARP_init |
; |
; This function resets all ARP variables |
; |
;----------------------------------------------------------------- |
macro ARP_init { |
xor eax, eax |
mov edi, ARP_entries_num |
mov ecx, 4*NET_DEVICES_MAX |
rep stosd |
} |
;--------------------------------------------------------------------------- |
; |
; ARP_decrease_entry_ttls |
; |
;--------------------------------------------------------------------------- |
macro ARP_decrease_entry_ttls { |
local .loop |
local .exit |
; The TTL field is decremented every second, and is deleted when it reaches 0. |
; It is refreshed every time a packet is received. |
; If the TTL field is 0xFFFF it is a static entry and is never deleted. |
; The status field can be the following values: |
; 0x0000 entry not used |
; 0x0001 entry holds a valid mapping |
; 0x0002 entry contains an IP address, awaiting ARP response |
; 0x0003 No response received to ARP request. |
; The last status value is provided to allow the network layer to delete |
; a packet that is queued awaiting an ARP response |
xor edi, edi |
.loop_outer: |
mov ecx, [ARP_entries_num + 4*edi] |
test ecx, ecx |
jz .exit |
mov esi, (ARP_TABLE_SIZE * sizeof.ARP_entry) |
imul esi, edi |
add esi, ARP_table |
.loop: |
cmp [esi + ARP_entry.TTL], ARP_STATIC_ENTRY |
je .next |
dec [esi + ARP_entry.TTL] |
jz .time_out |
.next: |
add esi, sizeof.ARP_entry |
dec ecx |
jnz .loop |
jmp .exit |
.time_out: |
cmp [esi + ARP_entry.Status], ARP_AWAITING_RESPONSE |
je .response_timeout |
push esi edi ecx |
call ARP_del_entry |
pop ecx edi esi |
jmp .next |
.response_timeout: |
mov [esi + ARP_entry.Status], ARP_RESPONSE_TIMEOUT |
mov [esi + ARP_entry.TTL], 10 |
jmp .next |
.exit: |
inc edi |
cmp edi, NET_DEVICES_MAX |
jb .loop_outer |
} |
;----------------------------------------------------------------- |
; |
; ARP_input |
; |
; IN: Pointer to buffer in [esp] |
; size of buffer in [esp+4] |
; packet size (without ethernet header) in ecx |
; packet ptr in edx |
; device ptr in ebx |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
ARP_input: |
;----------------------------------------- |
; Check validity and print some debug info |
cmp ecx, sizeof.ARP_header |
jb .exit |
call NET_ptr_to_num4 |
cmp edi, -1 |
jz .exit |
inc [ARP_PACKETS_RX + edi] ; update stats |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: got packet from %u.%u.%u.%u (device*4=%u)\n",\ |
[edx + ARP_header.SenderIP]:1, [edx + ARP_header.SenderIP + 1]:1,\ |
[edx + ARP_header.SenderIP + 2]:1, [edx + ARP_header.SenderIP + 3]:1, edi |
;------------------------------ |
; First, check for IP collision |
mov eax, [edx + ARP_header.SenderIP] |
cmp eax, [IP_LIST + edi] |
je .collision |
;--------------------- |
; Handle reply packets |
cmp [edx + ARP_header.Opcode], ARP_REP_OPCODE |
jne .maybe_request |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: It's a reply\n" |
mov ecx, [ARP_entries_num + edi] |
test ecx, ecx |
jz .exit |
mov esi, edi |
imul esi, (ARP_TABLE_SIZE * sizeof.ARP_entry)/4 |
add esi, ARP_table |
.loop: |
cmp [esi + ARP_entry.IP], eax |
je .gotit |
add esi, sizeof.ARP_entry |
dec ecx |
jnz .loop |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: no matching entry found\n" |
jmp .exit |
.gotit: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: found matching entry\n" |
cmp [esi + ARP_entry.TTL], ARP_STATIC_ENTRY ; if it is a static entry, dont touch it |
je .exit |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: updating entry\n" |
mov [esi + ARP_entry.Status], ARP_VALID_MAPPING |
mov [esi + ARP_entry.TTL], ARP_ENTRY_TTL |
mov eax, dword [edx + ARP_header.SenderMAC] |
mov dword [esi + ARP_entry.MAC], eax |
mov cx, word [edx + ARP_header.SenderMAC + 4] |
mov word [esi + ARP_entry.MAC + 4], cx |
jmp .exit |
;----------------------- |
; Handle request packets |
.maybe_request: |
cmp [edx + ARP_header.Opcode], ARP_REQ_OPCODE |
jne .exit |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: its a request\n" |
mov eax, [IP_LIST + edi] |
cmp eax, [edx + ARP_header.TargetIP] ; Is it looking for my IP address? |
jne .exit |
push eax |
push edi |
; OK, it is a request for one of our MAC addresses. |
; Build the frame and send it. We can reuse the buffer. (faster then using ARP_create_packet) |
lea esi, [edx + ARP_header.SenderMAC] |
lea edi, [edx + ARP_header.TargetMAC] |
movsd ; Move Sender Mac to Dest MAC |
movsw ; |
movsd ; Move sender IP to Dest IP |
pop esi |
mov esi, [NET_DRV_LIST + esi] |
lea esi, [esi + ETH_DEVICE.mac] |
lea edi, [edx + ARP_header.SenderMAC] |
movsd ; Copy MAC address from in MAC_LIST |
movsw ; |
pop eax |
stosd ; Write our IP |
mov [edx + ARP_header.Opcode], ARP_REP_OPCODE |
; Now, Fill in ETHERNET header |
mov edi, [esp] |
lea esi, [edx + ARP_header.TargetMAC] |
movsd |
movsw |
lea esi, [edx + ARP_header.SenderMAC] |
movsd |
movsw |
; mov ax , ETHER_ARP ; It's already there, I'm sure of it! |
; stosw |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: Sending reply\n" |
call [ebx + NET_DEVICE.transmit] |
ret |
.collision: |
inc [ARP_CONFLICTS + edi] |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: IP address conflict detected!\n" |
.exit: |
call NET_packet_free |
add esp, 4 ; pop (balance stack) |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_input: exiting\n" |
ret |
;--------------------------------------------------------------------------- |
; |
; ARP_output_request |
; |
; IN: ebx = device ptr |
; eax = IP |
; OUT: / |
; scratched: probably everything |
; |
;--------------------------------------------------------------------------- |
align 4 |
ARP_output_request: |
push eax |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_output_request: ip=%u.%u.%u.%u device=0x%x\n",\ |
[esp]:1, [esp + 1]:1, [esp + 2]:1, [esp + 3]:1, ebx |
lea eax, [ebx + ETH_DEVICE.mac] ; local device mac |
mov edx, ETH_BROADCAST ; broadcast mac |
mov ecx, sizeof.ARP_header |
mov di, ETHER_PROTO_ARP |
call ETH_output |
jz .exit |
mov [edi + ARP_header.HardwareType], 0x0100 ; Ethernet |
mov [edi + ARP_header.ProtocolType], 0x0008 ; IP |
mov [edi + ARP_header.HardwareSize], 6 ; MAC-addr length |
mov [edi + ARP_header.ProtocolSize], 4 ; IP-addr length |
mov [edi + ARP_header.Opcode], ARP_REQ_OPCODE ; Request |
add edi, ARP_header.SenderMAC |
lea esi, [ebx + ETH_DEVICE.mac] ; SenderMac |
movsw ; |
movsd ; |
push edi |
call NET_ptr_to_num4 |
inc [ARP_PACKETS_TX + edi] ; assume we will succeed |
lea esi, [IP_LIST + edi] ; SenderIP |
pop edi |
movsd |
mov esi, ETH_BROADCAST ; DestMac |
movsw ; |
movsd ; |
popd [edi] ; DestIP |
push edx eax |
call [ebx + NET_DEVICE.transmit] |
ret |
.exit: |
add esp, 4 |
DEBUGF DEBUG_NETWORK_ERROR, "ARP_output_request: send failed\n" |
ret |
;----------------------------------------------------------------- |
; |
; ARP_add_entry (or update) |
; |
; IN: esi = ptr to entry (can easily be made on the stack) |
; edi = device num*4 |
; OUT: eax = entry #, -1 on error |
; esi = ptr to newly created entry |
; |
;----------------------------------------------------------------- ; TODO: use a mutex |
align 4 |
ARP_add_entry: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_add_entry: device=%u\n", edi |
mov ecx, [ARP_entries_num + edi] |
cmp ecx, ARP_TABLE_SIZE ; list full ? |
jae .full |
; From this point on, we can only fail if IP has a static entry, or if table is corrupt. |
inc [ARP_entries_num + edi] ; assume we will succeed |
push edi |
xor ecx, ecx |
imul edi, ARP_TABLE_SIZE*sizeof.ARP_entry/4 |
add edi, ARP_table |
mov eax, [esi + ARP_entry.IP] |
.loop: |
cmp [edi + ARP_entry.Status], ARP_NO_ENTRY ; is this slot empty? |
je .add |
cmp [edi + ARP_entry.IP], eax ; if not, check if it doesnt collide |
jne .maybe_next |
cmp [edi + ARP_entry.TTL], ARP_STATIC_ENTRY ; ok, its the same IP, update it if not static |
jne .add |
DEBUGF DEBUG_NETWORK_ERROR, "ARP_add_entry: failed, IP already has a static entry\n" |
jmp .error |
.maybe_next: ; try the next slot |
add edi, sizeof.ARP_entry |
inc ecx |
cmp ecx, ARP_TABLE_SIZE |
jb .loop |
.add: |
push ecx |
mov ecx, sizeof.ARP_entry/2 |
rep movsw |
pop ecx |
lea esi, [edi - sizeof.ARP_entry] |
pop edi |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_add_entry: entry=%u\n", ecx |
ret |
.error: |
pop edi |
dec [ARP_entries_num + edi] |
DEBUGF DEBUG_NETWORK_ERROR, "ARP_add_entry_failed\n" |
.full: |
mov eax, -1 |
ret |
;----------------------------------------------------------------- |
; |
; ARP_del_entry |
; |
; IN: esi = ptr to arp entry |
; edi = device number |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
ARP_del_entry: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_del_entry: entry=%x entrys=%u\n", esi, [ARP_entries_num + 4*edi] |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_del_entry: IP=%u.%u.%u.%u\n", \ |
[esi + ARP_entry.IP]:1, [esi + ARP_entry.IP + 1]:1, [esi + ARP_entry.IP + 2]:1, [esi + ARP_entry.IP + 3]:1 |
push edi |
imul edi, (ARP_TABLE_SIZE) * sizeof.ARP_entry |
lea ecx, [ARP_table + (ARP_TABLE_SIZE - 1) * sizeof.ARP_entry + edi] |
sub ecx, esi |
shr ecx, 1 |
; move all trailing entries, sizeof.ARP_entry bytes to left. |
mov edi, esi |
add esi, sizeof.ARP_entry |
rep movsw |
; now add an empty entry to the end (erasing previous one) |
xor eax, eax |
mov ecx, sizeof.ARP_entry/2 |
rep stosw |
pop edi |
dec [ARP_entries_num + 4*edi] |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_del_entry: success\n" |
ret |
;----------------------------------------------------------------- |
; |
; ARP_IP_to_MAC |
; |
; This function translates an IP address to a MAC address |
; |
; IN: eax = IPv4 address |
; edi = device number * 4 |
; OUT: eax = -1 on error, -2 means request send |
; else, ax = first two bytes of mac (high 16 bits of eax will be 0) |
; ebx = last four bytes of mac |
; edi = unchanged |
; |
;----------------------------------------------------------------- |
align 4 |
ARP_IP_to_MAC: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_IP_to_MAC: %u.%u", al, ah |
rol eax, 16 |
DEBUGF DEBUG_NETWORK_VERBOSE, ".%u.%u device*4: %u\n", al, ah, edi |
rol eax, 16 |
cmp eax, 0xffffffff |
je .broadcast |
;-------------------------------- |
; Try to find the IP in ARP_table |
mov ecx, [ARP_entries_num + edi] |
test ecx, ecx |
jz .not_in_list |
mov esi, edi |
imul esi, (sizeof.ARP_entry * ARP_TABLE_SIZE)/4 |
add esi, ARP_table + ARP_entry.IP |
.scan_loop: |
cmp [esi], eax |
je .found_it |
add esi, sizeof.ARP_entry |
dec ecx |
jnz .scan_loop |
.not_in_list: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_IP_to_MAC: preparing for ARP request\n" |
push eax edi ; save IP for ARP_output_request |
; Now craft the ARP entry on the stack |
pushw ARP_REQUEST_TTL ; TTL |
pushw ARP_AWAITING_RESPONSE ; status |
pushd 0 ; mac |
pushw 0 |
pushd eax ; ip |
mov esi, esp |
; Add it to the list |
call ARP_add_entry |
; Delete the temporary entry |
add esp, sizeof.ARP_entry ; clear the entry from stack |
; If we could not add it to the list, give up |
cmp eax, -1 ; did ARP_add_entry fail? |
je .full |
;----------------------------------------------- |
; At this point, we got an ARP entry in the list |
; Now send a request packet on the network |
pop edi eax ; IP in eax, device number in ebx, for ARP_output_request |
push esi edi |
mov ebx, [NET_DRV_LIST + edi] |
call ARP_output_request |
pop edi esi |
.found_it: |
cmp [esi + ARP_entry.Status], ARP_VALID_MAPPING ; Does it have a MAC assigned? |
je .valid |
if ARP_BLOCK |
cmp [esi + ARP_entry.Status], ARP_AWAITING_RESPONSE ; Are we waiting for reply from remote end? |
jne .give_up |
push esi |
mov esi, 10 ; wait 10 ms |
call delay_ms |
pop esi |
jmp .found_it ; now check again |
else |
jmp .give_up |
end if |
.valid: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_IP_to_MAC: found MAC\n" |
movzx eax, word[esi + ARP_entry.MAC] |
mov ebx, dword[esi + ARP_entry.MAC + 2] |
ret |
.full: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_IP_to_MAC: table is full!\n" |
add esp, 8 |
.give_up: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ARP_IP_to_MAC: entry has no valid mapping!\n" |
mov eax, -1 |
ret |
.broadcast: |
mov eax, 0x0000ffff |
mov ebx, 0xffffffff |
ret |
;----------------------------------------------------------------- |
; |
; ARP_API |
; |
; This function is called by system function 76 |
; |
; IN: subfunction number in bl |
; device number in bh |
; ecx, edx, .. depends on subfunction |
; |
; OUT: ? |
; |
;----------------------------------------------------------------- |
align 4 |
ARP_api: |
movzx eax, bh |
shl eax, 2 |
and ebx, 0xff |
cmp ebx, .number |
ja .error |
jmp dword [.table + 4*ebx] |
.table: |
dd .packets_tx ; 0 |
dd .packets_rx ; 1 |
dd .entries ; 2 |
dd .read ; 3 |
dd .write ; 4 |
dd .remove ; 5 |
dd .send_announce ; 6 |
dd .conflicts ; 7 |
.number = ($ - .table) / 4 - 1 |
.error: |
mov eax, -1 |
ret |
.packets_tx: |
mov eax, [ARP_PACKETS_TX + eax] |
ret |
.packets_rx: |
mov eax, [ARP_PACKETS_RX + eax] |
ret |
.conflicts: |
mov eax, [ARP_CONFLICTS + eax] |
ret |
.entries: |
mov eax, [ARP_entries_num + eax] |
ret |
.read: |
cmp ecx, [ARP_entries_num + eax] |
jae .error |
shr eax, 2 |
imul eax, sizeof.ARP_entry*ARP_TABLE_SIZE |
add eax, ARP_table |
; edi = pointer to buffer |
; ecx = # entry |
imul ecx, sizeof.ARP_entry |
lea esi, [eax + ecx] |
mov ecx, sizeof.ARP_entry/2 |
rep movsw |
xor eax, eax |
ret |
.write: |
; esi = pointer to buffer |
mov edi, eax |
call ARP_add_entry ; out: eax = entry number, -1 on error |
ret |
.remove: |
; ecx = # entry |
cmp ecx, [ARP_entries_num + eax] |
jae .error |
imul ecx, sizeof.ARP_entry |
lea esi, [ARP_table + ecx] |
mov edi, eax |
shr edi, 2 |
call ARP_del_entry |
ret |
.send_announce: |
mov ebx, [NET_DRV_LIST + eax] |
mov eax, [IP_LIST + eax] |
call ARP_output_request ; now send a gratuitous ARP |
ret |
/kernel/branches/kolibri-process/network/IPv4.inc |
---|
0,0 → 1,1084 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. 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: 3515 $ |
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 |
IP_LIST rd NET_DEVICES_MAX |
SUBNET_LIST rd NET_DEVICES_MAX |
DNS_LIST rd NET_DEVICES_MAX |
GATEWAY_LIST rd NET_DEVICES_MAX |
BROADCAST_LIST 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_FRAGMENT_LIST rb IPv4_MAX_FRAGMENTS * sizeof.IPv4_FRAGMENT_slot |
IPv4_ROUTES rd IPv4_MAX_ROUTES * sizeof.IPv4_ROUTE |
endg |
;----------------------------------------------------------------- |
; |
; IPv4_init |
; |
; This function resets all IP variables |
; |
;----------------------------------------------------------------- |
macro IPv4_init { |
xor eax, eax |
mov edi, IP_LIST |
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_FRAGMENT_LIST |
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: |
; |
; Will check if IPv4 Packet isnt damaged |
; and call appropriate handler. (TCP/UDP/ICMP/..) |
; |
; It will also re-construct fragmented packets |
; |
; IN: Pointer to buffer in [esp] |
; size of buffer in [esp+4] |
; pointer to device struct in ebx |
; pointer to IPv4 header in edx |
; size of IPv4 packet in ecx |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
IPv4_input: ; TODO: add IPv4 raw sockets support |
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 |
;------------------------------- |
; 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 is correct |
call NET_ptr_to_num4 |
; check if it matches local ip (Using RFC1122 strong end system model) |
mov eax, [edx + IPv4_header.DestinationAddress] |
cmp eax, [IP_LIST + edi] |
je .ip_ok |
; check for broadcast (IP or (not SUBNET)) |
cmp eax, [BROADCAST_LIST + edi] |
je .ip_ok |
; or a special broadcast (255.255.255.255) |
cmp eax, 0xffffffff |
je .ip_ok |
; maybe it's a multicast (224.0.0.0/4) |
and eax, 0x0fffffff |
cmp eax, 224 |
je .ip_ok |
; or a loopback address (127.0.0.0/8) |
and eax, 0x00ffffff |
cmp eax, 127 |
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 ; |
lea edi, [edx + IPv4_header.SourceAddress] ; make edi ptr to source and dest IPv4 address |
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 |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: unknown protocol %u\n", al |
.dump: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: dumping\n" |
inc [IPv4_packets_dumped] ; FIXME: use correct interface |
call NET_packet_free |
add esp, 4 ; pop (balance stack) |
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 + 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 + 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 + IPv4_FRAGMENT_entry.NextPtr], eax ; update pointer of previous entry to the new entry |
mov [eax + IPv4_FRAGMENT_entry.NextPtr], -1 |
mov [eax + IPv4_FRAGMENT_entry.PrevPtr], edi |
mov [eax + IPv4_FRAGMENT_entry.Owner], ebx |
add esp, 4 |
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_FRAGMENT_LIST |
.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 + IPv4_FRAGMENT_entry.NextPtr], -1 |
mov [eax + IPv4_FRAGMENT_entry.PrevPtr], -1 |
mov [eax + IPv4_FRAGMENT_entry.Owner], ebx |
add esp, 4 ; balance stack and exit |
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 |
xor eax, eax |
or edi, -1 |
.count_bytes: |
cmp [esi + 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.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.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 + IPv4_FRAGMENT_entry.NextPtr] |
cmp esi, -1 |
jne .count_bytes |
mov esi, [esp+4] |
mov [edi + IPv4_FRAGMENT_entry.NextPtr], esi ; Add this packet to the chain, this simplifies the following code |
mov [esi + IPv4_FRAGMENT_entry.NextPtr], -1 |
mov [esi + IPv4_FRAGMENT_entry.PrevPtr], edi |
mov [esi + 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 |
push eax |
call kernel_alloc |
test eax, eax |
je .destroy_slot_pop ; If we dont have enough space to allocate the buffer, discard all packets in slot |
mov edx, [esp+4] ; Get pointer to first fragment entry back in edx |
.rebuild_packet_loop: |
movzx ecx, [edx + 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.IPv4_FRAGMENT_entry + IPv4_header.VersionAndIHL] ; Find header size (in ebx) of fragment |
and bx, 0x000F ; |
shl bx, 2 ; |
lea esi, [edx + sizeof.IPv4_FRAGMENT_entry] ; Set esi to the correct begin of fragment |
movzx ecx, [edx + 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 ; |
.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 + IPv4_FRAGMENT_entry.Owner] ; we need to remeber the owner, in case this is the last packet |
push [edx + 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_packet_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 |
xchg cl, ch |
mov edx, eax |
mov [edx + IPv4_header.TotalLength], cx |
add esp, 12 |
xchg cl, ch |
push ecx edx ; size and pointer |
jmp .handle_it ; edx = buf ptr, ecx = size, [esp] buf ptr, [esp+4], total size, ebx=device ptr |
.destroy_slot_pop: |
add esp, 4 |
.destroy_slot: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_input: Destroy fragment slot!\n" |
; TODO! |
jmp .dump |
;----------------------------------------------------------------- |
; |
; 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_FRAGMENT_LIST |
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: eax = Destination IP |
; ecx = data length |
; edx = Source IP |
; di = TTL shl 8 + protocol |
; |
; OUT: eax = pointer to buffer start |
; ebx = pointer to device struct (needed for sending procedure) |
; ecx = unchanged (packet size of embedded data) |
; edx = size of complete buffer |
; edi = pointer to start of data (0 on error) |
; |
;------------------------------------------------------------------ |
align 4 |
IPv4_output: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_output: size=%u ip=0x%x\n", ecx, eax |
cmp ecx, 65500 ; Max IPv4 packet size |
ja .too_large |
push ecx di eax |
call IPv4_route ; outputs device number in edi, dest ip in eax, source IP in edx |
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 ebx, [NET_DRV_LIST + edi] |
lea eax, [ebx + ETH_DEVICE.mac] |
mov edx, esp |
mov ecx, [esp + 6 + 8 + 2] |
add ecx, sizeof.IPv4_header |
mov di, ETHER_PROTO_IPv4 |
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 edi, edi |
ret |
.arp_error: |
DEBUGF DEBUG_NETWORK_ERROR, "IPv4_output: ARP error=%x\n", eax |
add esp, 3*4+2 |
xor edi, edi |
ret |
.too_large: |
DEBUGF DEBUG_NETWORK_ERROR, "IPv4_output: Packet too large!\n" |
xor edi, edi |
ret |
.loopback: |
mov dword [esp + 2], eax ; change 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: / |
; |
;------------------------------------------------------------------ |
align 4 |
IPv4_output_raw: |
DEBUGF 1,"IPv4_output_raw: size=%u ptr=%x socket=%x\n", ecx, esi, eax |
cmp ecx, 1480 ;;;;; FIXME |
ja .too_large |
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 ebx, [NET_DRV_LIST + 4*edi] |
lea eax, [ebx + ETH_DEVICE.mac] |
mov edx, esp |
mov ecx, [esp + 6 + 4] |
add ecx, sizeof.IPv4_header |
mov di, ETHER_PROTO_IPv4 |
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 |
.arp_error: |
add esp, 8+4+4 |
.too_large: |
DEBUGF DEBUG_NETWORK_ERROR, "IPv4_output_raw: Failed\n" |
sub edi, edi |
ret |
;-------------------------------------------------------- |
; |
; |
; IN: dword [esp] = pointer to buffer containing ipv4 packet to be fragmented |
; dword [esp+4] = buffer size |
; esi = pointer to ip header in that buffer |
; ecx = max size of fragments |
; |
; OUT: / |
; |
;-------------------------------------------------------- |
align 4 |
IPv4_fragment: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_fragment\n" |
and ecx, not 111b ; align 4 |
cmp ecx, sizeof.IPv4_header + 8 ; must be able to put at least 8 bytes |
jb .err2 |
push esi ecx |
mov eax, [esi + IPv4_header.DestinationAddress] |
call ARP_IP_to_MAC |
pop ecx esi |
cmp eax, -1 |
jz .err2 |
push ebx |
push ax |
mov ebx, [NET_DRV_LIST] |
lea eax, [ebx + ETH_DEVICE.mac] |
push eax |
push esi ; ptr to ip header |
sub ecx, sizeof.IPv4_header ; substract header size |
push ecx ; max data size |
push dword 0 ; offset |
.new_fragment: |
DEBUGF DEBUG_NETWORK_VERBOSE, "Ipv4_fragment: new fragment" |
mov eax, [esp + 3*4] |
lea ebx, [esp + 4*4] |
mov di , ETHER_PROTO_IPv4 |
call ETH_output |
cmp edi, -1 |
jz .err |
; copy header |
mov esi, [esp + 2*4] |
mov ecx, 5 ; 5 dwords: TODO: use IHL field of the header! |
rep movsd |
; copy data |
mov esi, [esp + 2*4] |
add esi, sizeof.IPv4_header |
add esi, [esp] ; offset |
mov ecx, [esp + 1*4] |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv4_fragment: copying %u bytes\n", ecx |
rep movsb |
; now, correct header |
mov ecx, [esp + 1*4] |
add ecx, sizeof.IPv4_header |
xchg cl, ch |
mov [edi + IPv4_header.TotalLength], cx |
mov ecx, [esp] ; offset |
xchg cl, ch |
; cmp dword[esp + 4*4], 0 ; last fragment?;<<<<<< |
; je .last_fragment |
or cx, 1 shl 2 ; more fragments |
; .last_fragment: |
mov [edi + IPv4_header.FlagsAndFragmentOffset], cx |
mov [edi + IPv4_header.HeaderChecksum], 0 |
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<< send the packet |
mov ecx, [esp + 1*4] |
push edx eax |
IPv4_checksum edi |
call [ebx + NET_DEVICE.transmit] |
;<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
mov ecx, [esp+4] |
add [esp], ecx |
mov ecx, [esp+3*4+6+4] ; ptr to begin of buff |
add ecx, [esp+3*4+6+4+4] ; buff size |
sub ecx, [esp+2*4] ; ptr to ip header |
add ecx, [esp] ; offset |
DEBUGF DEBUG_NETWORK_VERBOSE, "Ipv4_fragment: %u bytes remaining\n", ecx |
cmp ecx, [esp+1*4] |
jae .new_fragment |
mov [esp+4], ecx ; set fragment size to remaining packet size |
jmp .new_fragment |
.err: |
DEBUGF DEBUG_NETWORK_ERROR, "Ipv4_fragment: failed\n" |
.done: |
add esp, 12 + 4 + 6 |
.err2: |
DEBUGF DEBUG_NETWORK_VERBOSE, "Ipv4_fragment: dumping\n" |
call NET_packet_free |
add esp, 4 |
ret |
;--------------------------------------------------------------------------- |
; |
; IPv4_route |
; |
; IN: eax = Destination IP |
; edx = Source IP |
; OUT: eax = Destination IP (or gateway IP) |
; edx = Source IP |
; edi = device number*4 |
; DESTROYED: |
; ecx |
; |
;--------------------------------------------------------------------------- |
align 4 |
IPv4_route: ; TODO: return error if no valid route found |
cmp eax, 0xffffffff |
je .broadcast |
xor edi, edi |
.loop: |
mov ebx, [IP_LIST + edi] |
and ebx, [SUBNET_LIST + edi] |
jz .next |
mov ecx, eax |
and ecx, [SUBNET_LIST + edi] |
cmp ebx, ecx |
je .got_it |
.next: |
add edi, 4 |
cmp edi, 4*NET_DEVICES_MAX |
jb .loop |
mov eax, [GATEWAY_LIST + 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, [IP_LIST + edi] |
@@: |
ret |
;--------------------------------------------------------------------------- |
; |
; IPv4_get_frgmnt_num |
; |
; IN: / |
; OUT: fragment number in ax |
; |
;--------------------------------------------------------------------------- |
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 ok / -1 error |
; ebx = error code |
; |
;------------------------- |
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 [IP_LIST + 4] ; FIXME: use correct local IP |
pop [eax + IP_SOCKET.LocalIP] |
; Fill in remote IP |
pushd [edx + 4] |
pop [eax + IP_SOCKET.RemoteIP] |
; Set up data receiving queue |
push eax |
init_queue (eax + SOCKET_QUEUE_LOCATION) |
pop eax |
lea ecx, [eax + SOCKET.mutex] |
call mutex_unlock |
xor eax, eax |
ret |
;--------------------------------------------------------------------------- |
; |
; IPv4_API |
; |
; This function is called by system function 75 |
; |
; IN: subfunction number in bl |
; device number in bh |
; ecx, edx, .. depends on subfunction |
; |
; OUT: |
; |
;--------------------------------------------------------------------------- |
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, [IP_LIST + eax] |
ret |
.write_ip: |
mov [IP_LIST + eax], ecx |
mov edi, eax ; device number, we'll need it for ARP |
; pre-calculate the local broadcast address |
mov ebx, [SUBNET_LIST + eax] |
not ebx |
or ebx, ecx |
mov [BROADCAST_LIST + eax], ebx |
mov ebx, [NET_DRV_LIST + eax] |
mov eax, [IP_LIST + eax] |
call ARP_output_request ; now send a gratuitous ARP |
call NET_send_event |
xor eax, eax |
ret |
.read_dns: |
mov eax, [DNS_LIST + eax] |
ret |
.write_dns: |
mov [DNS_LIST + eax], ecx |
call NET_send_event |
xor eax, eax |
ret |
.read_subnet: |
mov eax, [SUBNET_LIST + eax] |
ret |
.write_subnet: |
mov [SUBNET_LIST + eax], ecx |
; pre-calculate the local broadcast address |
mov ebx, [IP_LIST + eax] |
not ecx |
or ecx, ebx |
mov [BROADCAST_LIST + eax], ecx |
call NET_send_event |
xor eax, eax |
ret |
.read_gateway: |
mov eax, [GATEWAY_LIST + eax] |
ret |
.write_gateway: |
mov [GATEWAY_LIST + eax], ecx |
call NET_send_event |
xor eax, eax |
ret |
/kernel/branches/kolibri-process/network/IPv6.inc |
---|
0,0 → 1,298 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2012-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; IPv6.INC ;; |
;; ;; |
;; Part of the tcp/ip network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3251 $ |
struct IPv6_header |
VersionTrafficFlow dd ? ; Version[0-3], Traffic class[4-11], Flow Label [12-31] |
PayloadLength dw ? ; 16 bits, unsigned length of payload (extension headers are part of this) |
NextHeader db ? ; Values are same as in IPv4 'Protocol' field |
HopLimit db ? ; Decremented by every node, packet is discarded when it reaches 0 |
SourceAddress rd 4 ; 128-bit addresses |
DestinationAddress rd 4 ; |
Payload rb 0 |
ends |
uglobal |
align 4 |
IPv6: |
.addresses rd 4*NET_DEVICES_MAX |
.subnet rd 4*NET_DEVICES_MAX |
.dns rd 4*NET_DEVICES_MAX |
.gateway rd 4*NET_DEVICES_MAX |
.packets_tx rd NET_DEVICES_MAX |
.packets_rx rd NET_DEVICES_MAX |
endg |
;----------------------------------------------------------------- |
; |
; IPv6_init |
; |
; This function resets all IP variables |
; |
;----------------------------------------------------------------- |
macro IPv6_init { |
xor eax, eax |
mov edi, IPv6 |
mov ecx, (4*4*4+2*4)MAX_IP |
rep stosd |
} |
;----------------------------------------------------------------- |
; |
; IPv6_input: |
; |
; Will check if IPv6 Packet isnt damaged |
; and call appropriate handler. (TCP/UDP/ICMP/..) |
; |
; It will also re-construct fragmented packets |
; |
; IN: Pointer to buffer in [esp] |
; size of buffer in [esp+4] |
; pointer to device struct in ebx |
; pointer to IPv6 header in edx |
; size of IPv6 packet in ecx |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
IPv6_input: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input from: %x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x\n",\ |
[edx + IPv6_header.SourceAddress + 0]:2,[edx + IPv6_header.SourceAddress + 1]:2,\ |
[edx + IPv6_header.SourceAddress + 2]:2,[edx + IPv6_header.SourceAddress + 3]:2,\ |
[edx + IPv6_header.SourceAddress + 4]:2,[edx + IPv6_header.SourceAddress + 5]:2,\ |
[edx + IPv6_header.SourceAddress + 6]:2,[edx + IPv6_header.SourceAddress + 7]:2,\ |
[edx + IPv6_header.SourceAddress + 8]:2,[edx + IPv6_header.SourceAddress + 9]:2,\ |
[edx + IPv6_header.SourceAddress + 10]:2,[edx + IPv6_header.SourceAddress + 11]:2,\ |
[edx + IPv6_header.SourceAddress + 12]:2,[edx + IPv6_header.SourceAddress + 13]:2,\ |
[edx + IPv6_header.SourceAddress + 14]:2,[edx + IPv6_header.SourceAddress + 15]:2 |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input to: %x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x\n",\ |
[edx + IPv6_header.DestinationAddress + 0]:2,[edx + IPv6_header.DestinationAddress + 1]:2,\ |
[edx + IPv6_header.DestinationAddress + 2]:2,[edx + IPv6_header.DestinationAddress + 3]:2,\ |
[edx + IPv6_header.DestinationAddress + 4]:2,[edx + IPv6_header.DestinationAddress + 5]:2,\ |
[edx + IPv6_header.DestinationAddress + 6]:2,[edx + IPv6_header.DestinationAddress + 7]:2,\ |
[edx + IPv6_header.DestinationAddress + 8]:2,[edx + IPv6_header.DestinationAddress + 9]:2,\ |
[edx + IPv6_header.DestinationAddress + 10]:2,[edx + IPv6_header.DestinationAddress + 11]:2,\ |
[edx + IPv6_header.DestinationAddress + 12]:2,[edx + IPv6_header.DestinationAddress + 13]:2,\ |
[edx + IPv6_header.DestinationAddress + 14]:2,[edx + IPv6_header.DestinationAddress + 15]:2 |
sub ecx, sizeof.IPv6_header |
jb .dump |
cmp cx, [edx + IPv6_header.PayloadLength] |
jb .dump |
;------------------------------------------------------------------- |
; No, it's just a regular IP packet, pass it to the higher protocols |
.handle_it: |
movzx ecx, [edx + IPv6_header.PayloadLength] |
lea edi, [edx + IPv6_header.SourceAddress] ; make edi ptr to source and dest IPv6 address |
lea esi, [edx + IPv6_header.Payload] ; make esi ptr to data |
mov al, [edx + IPv6_header.NextHeader] |
.scan: |
cmp al, 59 ; no next |
je .dump |
cmp al, 0 |
je .hop_by_hop |
cmp al, 43 |
je .routing |
cmp al, 44 |
je .fragment |
cmp al, 60 |
je .dest_opts |
; cmp al, IP_PROTO_TCP |
; je TCP_input |
; cmp al, IP_PROTO_UDP |
; je UDP_input |
; cmp al, 58 |
; je ICMP6_input |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input - unknown protocol: %u\n", al |
.dump: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input - dumping\n" |
call kernel_free |
add esp, 4 |
ret |
.dump_options: |
add esp, 2+4+4 |
jmp .dump |
.nextheader: |
pop esi |
pop ecx |
pop ax |
jmp .scan |
;------------------------- |
; Hop-by-Hop |
.hop_by_hop: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input - hop by hop\n" |
pushw [esi] ; 8 bit identifier for option type |
movzx eax, byte[esi + 1] ; Hdr Ext Len |
inc eax ; first 8 octets not counted |
shl eax, 3 ; * 8 |
sub ecx, eax |
push ecx |
add eax, esi |
push eax |
inc esi |
inc esi |
mov al, [esi] |
cmp al, 0 |
je .pad_1 |
cmp al, 1 |
je .pad_n |
; TODO: check with other known options |
; unknown option.. discard packet or not? |
; check highest two bits |
test al, 0xc0 ; discard packet |
jnz .dump_options |
.pad_n: |
movzx eax, byte[esi + 1] |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input - pad %u\n", eax |
inc esi |
inc esi |
add esi, eax |
sub ecx, eax |
jmp .hop_by_hop |
.pad_1: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input - pad 1\n" |
inc esi |
dec ecx |
jmp .hop_by_hop |
.dest_opts: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input - dest opts\n" |
jmp .nextheader |
.routing: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input - routing\n" |
pushw [esi] ; 8 bit identifier for option type |
movzx eax, byte[esi + 1] ; Hdr Ext Len |
inc eax ; first 8 octets not counted |
shl eax, 3 ; * 8 |
sub ecx, eax |
push ecx |
add eax, esi |
push eax |
inc esi |
inc esi |
cmp al, 0 |
je .pad_1 |
cmp al, 1 |
je .pad_n |
mov al, [esi] ; routing type |
jmp .nextheader |
.fragment: |
DEBUGF DEBUG_NETWORK_VERBOSE, "IPv6_input - fragment\n" |
jmp .nextheader |
;--------------------------------------------------------------------------- |
; |
; IPv6_API |
; |
; This function is called by system function 75 |
; |
; IN: subfunction number in bl |
; device number in bh |
; ecx, edx, .. depends on subfunction |
; |
; OUT: |
; |
;--------------------------------------------------------------------------- |
align 4 |
IPv6_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, [IPv6.packets_tx + eax] |
ret |
.packets_rx: |
mov eax, [IPv6.packets_rx + eax] |
ret |
/kernel/branches/kolibri-process/network/PPPoE.inc |
---|
0,0 → 1,358 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2012-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; PPPoE.INC ;; |
;; ;; |
;; Part of the tcp/ip network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
struct PPPoE_frame |
VersionAndType db ? |
Code db ? |
SessionID dw ? |
Length dw ? ; Length of payload, does NOT include the length PPPoE header. |
Payload rb 0 |
ends |
uglobal |
align 4 |
PPPoE_SID dw ? |
PPPoE_MAC dp ? |
endg |
;----------------------------------------------------------------- |
; |
; PPPoE_init |
; |
; This function resets all IP variables |
; |
;----------------------------------------------------------------- |
macro PPPoE_init { |
call PPPoE_stop_connection |
} |
;----------------------------------------------------------------- |
; |
; PPPoE discovery input |
; |
; Handler of received Ethernet packet with type = Discovery |
; |
; |
; IN: Pointer to buffer in [esp] |
; size of buffer in [esp+4] |
; pointer to device struct in ebx |
; pointer to PPP header in edx |
; size of PPP packet in ecx |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
PPPoE_discovery_input: |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_discovery_input\n" |
; First, find open PPPoE socket |
pusha |
mov ecx, socket_mutex |
call mutex_lock |
popa |
mov eax, net_sockets |
.next_socket: |
mov eax, [eax + SOCKET.NextPtr] |
or eax, eax |
jz .dump |
cmp [eax + SOCKET.Domain], AF_PPP |
jne .next_socket |
cmp [eax + SOCKET.Protocol], PPP_PROTO_ETHERNET |
jne .next_socket |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
; Now, send it to the this socket |
mov ecx, [esp + 4] |
mov esi, [esp] |
jmp SOCKET_input |
.dump: |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, 'PPPoE_discovery_input: dumping\n' |
call NET_packet_free |
add esp, 4 |
ret |
;-------------------------------------- |
; |
; Send discovery packet |
; |
; IN: eax = socket pointer |
; ecx = number of bytes to send |
; esi = pointer to data |
; |
;-------------------------------------- |
align 4 |
PPPoE_discovery_output: |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_discovery_output: socket=%x buffer=%x size=%d\n", eax, esi, ecx |
; RFC2516: An entire PADI packet (including the PPPoE header) MUST NOT |
; exceed 1484 octets. |
cmp ecx, 1484 + 14 |
ja .bad |
; Check that device exists and is ethernet device |
mov ebx, [eax + SOCKET.device] |
cmp ebx, NET_DEVICES_MAX |
ja .bad |
mov ebx, [NET_DRV_LIST + 4*ebx] |
test ebx, ebx |
jz .bad |
cmp [ebx + NET_DEVICE.device_type], NET_DEVICE_ETH |
jne .bad |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_discovery_output: device=%x\n", ebx |
; Create packet. |
push ecx esi |
stdcall kernel_alloc, 1500 |
pop esi ecx |
test eax, eax |
jz .bad |
mov edx, ecx |
mov edi, eax |
rep movsb |
cmp edx, 60 ; Min ETH size |
ja @f |
mov edx, 60 |
@@: |
push edx eax ; size and packet ptr for driver send proc |
; Overwrite source MAC and protocol type |
lea edi, [eax + ETH_header.SrcMAC] |
lea esi, [ebx + ETH_DEVICE.mac] |
movsd |
movsw |
cmp word[edi], ETHER_PROTO_PPP_SESSION ; Allow only PPP_discovery, or LCP |
je @f |
mov ax, ETHER_PROTO_PPP_DISCOVERY |
stosw |
@@: |
; And send the packet |
call [ebx + NET_DEVICE.transmit] |
xor eax, eax |
ret |
.bad: |
or eax, -1 |
ret |
;----------------------------------------------------------------- |
; |
; PPPoE session input |
; |
; Handler of received Ethernet packet with type = Session |
; |
; |
; IN: Pointer to buffer in [esp] |
; size of buffer in [esp+4] |
; pointer to device struct in ebx |
; pointer to PPP header in edx |
; size of PPP packet in ecx |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
PPPoE_session_input: |
cmp [edx + PPPoE_frame.VersionAndType], 0x11 |
jne .dump |
cmp [edx + PPPoE_frame.Code], 0x00 |
jne .dump |
movzx ecx, [edx + PPPoE_frame.Length] |
xchg cl, ch |
mov ax, [edx + PPPoE_frame.SessionID] |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_input: session ID=%x, length=%u\n", ax, cx |
cmp ax, [PPPoE_SID] |
jne .dump |
mov ax, word [edx + PPPoE_frame.Payload] |
add edx, PPPoE_frame.Payload + 2 |
cmp ax, PPP_PROTO_IPv4 |
je IPv4_input |
; cmp ax, PPP_PROTO_IPv6 |
; je IPv6_input |
jmp PPPoE_discovery_input ; Send LCP,CHAP,CBCP,... packets to the PPP dialer |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_input: Unknown protocol=%x\n", ax |
.dump: |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_input: dumping\n" |
call NET_packet_free |
add esp, 4 |
ret |
;----------------------------------------------------------------- |
; |
; PPPoE_output |
; |
; IN: |
; ebx = device ptr |
; ecx = packet size |
; |
; di = protocol |
; |
; OUT: edi = 0 on error, pointer to buffer otherwise |
; eax = buffer start |
; ebx = to device structure |
; ecx = unchanged (packet size of embedded data) |
; edx = size of complete buffer |
; |
;----------------------------------------------------------------- |
align 4 |
PPPoE_output: |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_output: size=%u device=%x\n", ecx, ebx |
pushw di |
pushw [PPPoE_SID] |
lea eax, [ebx + ETH_DEVICE.mac] |
lea edx, [PPPoE_MAC] |
add ecx, PPPoE_frame.Payload + 2 |
mov di, ETHER_PROTO_PPP_SESSION |
call ETH_output |
jz .eth_error |
sub ecx, PPPoE_frame.Payload |
mov [edi + PPPoE_frame.VersionAndType], 0x11 |
mov [edi + PPPoE_frame.Code], 0 |
popw [edi + PPPoE_frame.SessionID] |
xchg cl, ch |
mov [edi + PPPoE_frame.Length], cx |
xchg cl, ch |
pop word [edi + PPPoE_frame.Payload] |
sub ecx, 2 |
add edi, PPPoE_frame.Payload + 2 |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_output: success!\n" |
ret |
.eth_error: |
add esp, 4 |
xor edi, edi |
ret |
PPPoE_start_connection: |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_start_connection: %x\n", cx |
cmp [PPPoE_SID], 0 |
jne .fail |
mov [PPPoE_SID], cx |
mov dword [PPPoE_MAC], edx |
mov word [PPPoE_MAC + 4], si |
xor eax, eax |
ret |
.fail: |
or eax, -1 |
ret |
align 4 |
PPPoE_stop_connection: |
DEBUGF DEBUG_NETWORK_VERBOSE, "PPPoE_stop_connection\n" |
xor eax, eax |
mov [PPPoE_SID], ax |
mov dword [PPPoE_MAC], eax |
mov word [PPPoE_MAC + 4], ax |
ret |
;--------------------------------------------------------------------------- |
; |
; PPPoE API |
; |
; This function is called by system function 75 |
; |
; IN: subfunction number in bl |
; device number in bh |
; ecx, edx, .. depends on subfunction |
; |
; OUT: |
; |
;--------------------------------------------------------------------------- |
align 4 |
PPPoE_api: |
movzx eax, bh |
shl eax, 2 |
and ebx, 0xff |
cmp ebx, .number |
ja .error |
jmp dword [.table + 4*ebx] |
.table: |
dd PPPoE_start_connection ; 0 |
dd PPPoE_stop_connection ; 1 |
.number = ($ - .table) / 4 - 1 |
.error: |
mov eax, -1 |
ret |
/kernel/branches/kolibri-process/network/ethernet.inc |
---|
0,0 → 1,284 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; ETHERNET.INC ;; |
;; ;; |
;; Ethernet network layer for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3346 $ |
ETH_FRAME_MINIMUM = 60 |
ETH_QUEUE_SIZE = 255 |
struct ETH_header |
DstMAC dp ? ; destination MAC-address |
SrcMAC dp ? ; source MAC-address |
Type dw ? ; type of the upper-layer protocol |
ends |
struct ETH_DEVICE NET_DEVICE |
mac dp ? |
ends |
struct ETH_queue_entry |
device dd ? |
packet dd ? |
size dd ? |
ends |
iglobal |
align 4 |
ETH_BROADCAST dp 0xffffffffffff |
endg |
uglobal |
align 4 |
ETH_input_event dd ? |
ETH_queue rd (ETH_QUEUE_SIZE*sizeof.ETH_queue_entry + sizeof.queue)/4 |
endg |
macro ETH_init { |
init_queue ETH_queue |
movi ebx, 1 |
mov ecx, ETH_process_input |
call new_sys_threads |
test eax, eax |
jns @f |
DEBUGF DEBUG_NETWORK_ERROR,'K : cannot create kernel thread for ethernet, error %d\n', eax |
@@: |
} |
;----------------------------------------------------------------- |
; |
; ETH_input |
; |
; This function is called by ethernet drivers, |
; It pushes the received ethernet packets onto the eth_in_queue |
; |
; IN: [esp] = Pointer to buffer |
; [esp+4] = size of buffer |
; ebx = pointer to eth_device |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
ETH_input: |
push ebx |
mov esi, esp |
pushf |
cli |
add_to_queue ETH_queue, ETH_QUEUE_SIZE, sizeof.ETH_queue_entry, .fail |
popf |
add esp, sizeof.ETH_queue_entry |
xor edx, edx |
mov eax, [ETH_input_event] |
mov ebx, [eax + EVENT.id] |
xor esi, esi |
call raise_event |
ret |
.fail: |
popf |
DEBUGF DEBUG_NETWORK_VERBOSE, "ETH incoming queue is full, discarding packet!\n" |
add esp, sizeof.ETH_queue_entry - 8 |
call NET_packet_free |
add esp, 4 |
ret |
align 4 |
ETH_process_input: |
xor esi, esi |
mov ecx, MANUAL_DESTROY |
call create_event |
mov [ETH_input_event], eax |
.wait: |
mov eax, [ETH_input_event] |
mov ebx, [eax + EVENT.id] |
call wait_event |
.loop: |
get_from_queue ETH_queue, ETH_QUEUE_SIZE, sizeof.ETH_queue_entry, .wait |
mov eax, [esi + ETH_queue_entry.packet] |
mov ecx, [esi + ETH_queue_entry.size] |
mov ebx, [esi + ETH_queue_entry.device] |
pushd .loop ; return address |
push ecx eax |
DEBUGF DEBUG_NETWORK_VERBOSE, "ETH_input: size=%u\n", ecx |
sub ecx, sizeof.ETH_header |
jb .dump |
lea edx, [eax + sizeof.ETH_header] |
mov ax, [eax + ETH_header.Type] |
cmp ax, ETHER_PROTO_IPv4 |
je IPv4_input |
cmp ax, ETHER_PROTO_ARP |
je ARP_input |
cmp ax, ETHER_PROTO_IPv6 |
je IPv6_input |
cmp ax, ETHER_PROTO_PPP_DISCOVERY |
je PPPoE_discovery_input |
cmp ax, ETHER_PROTO_PPP_SESSION |
je PPPoE_session_input |
DEBUGF DEBUG_NETWORK_VERBOSE, "ETH_input: Unknown packet type=%x\n", ax |
.dump: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ETH_input: dumping\n" |
call NET_packet_free |
add esp, 4 |
ret |
;----------------------------------------------------------------- |
; |
; ETH_output |
; |
; IN: eax = pointer to source mac |
; ebx = device ptr |
; ecx = packet size |
; edx = pointer to destination mac |
; di = protocol |
; |
; OUT: edi = 0 on error, pointer to buffer otherwise |
; eax = buffer start |
; ebx = to device structure |
; ecx = unchanged (packet size of embedded data) |
; edx = size of complete buffer |
; |
;----------------------------------------------------------------- |
align 4 |
ETH_output: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ETH_output: size=%u device=%x\n", ecx, ebx |
cmp ecx, [ebx + NET_DEVICE.mtu] |
ja .exit |
push ecx |
push di eax edx |
add ecx, sizeof.ETH_header |
stdcall kernel_alloc, ecx |
test eax, eax |
jz .out_of_ram |
mov edi, eax |
pop esi |
movsd |
movsw |
pop esi |
movsd |
movsw |
pop ax |
stosw |
lea eax, [edi - sizeof.ETH_header] ; Set eax to buffer start |
pop ecx |
lea edx, [ecx + sizeof.ETH_header] ; Set edx to complete buffer size |
cmp edx, ETH_FRAME_MINIMUM |
jbe .adjust_size |
.done: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ETH_output: ptr=%x size=%u\n", eax, edx |
ret |
.adjust_size: |
mov edx, ETH_FRAME_MINIMUM |
test edx, edx ; clear zero flag |
jmp .done |
.out_of_ram: |
DEBUGF DEBUG_NETWORK_ERROR, "ETH_output: Out of ram!\n" |
add esp, 4+4+2+4 |
sub edi, edi |
ret |
.exit: |
DEBUGF DEBUG_NETWORK_ERROR, "ETH_output: Packet too large!\n" |
sub edi, edi |
ret |
;----------------------------------------------------------------- |
; |
; ETH_API |
; |
; This function is called by system function 76 |
; |
; IN: subfunction number in bl |
; device number in bh |
; ecx, edx, .. depends on subfunction |
; |
; OUT: |
; |
;----------------------------------------------------------------- |
align 4 |
ETH_api: |
cmp bh, NET_DEVICES_MAX |
ja .error |
movzx eax, bh |
mov eax, dword [NET_DRV_LIST + 4*eax] |
cmp [eax + NET_DEVICE.device_type], NET_DEVICE_ETH |
jne .error |
and ebx, 0xff |
cmp ebx, .number |
ja .error |
jmp dword [.table + 4*ebx] |
.table: |
dd .read_mac ; 0 |
.number = ($ - .table) / 4 - 1 |
.error: |
or eax, -1 |
ret |
.read_mac: |
movzx ebx, word [eax + ETH_DEVICE.mac] |
mov eax, dword [eax + ETH_DEVICE.mac + 2] |
mov [esp+20+4], ebx ; TODO: fix this ugly code |
ret |
/kernel/branches/kolibri-process/network/icmp.inc |
---|
0,0 → 1,469 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; ICMP.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: 2924 $ |
; ICMP types & codes |
ICMP_ECHOREPLY = 0 ; echo reply message |
ICMP_UNREACH = 3 |
ICMP_UNREACH_NET = 0 ; bad net |
ICMP_UNREACH_HOST = 1 ; bad host |
ICMP_UNREACH_PROTOCOL = 2 ; bad protocol |
ICMP_UNREACH_PORT = 3 ; bad port |
ICMP_UNREACH_NEEDFRAG = 4 ; IP_DF caused drop |
ICMP_UNREACH_SRCFAIL = 5 ; src route failed |
ICMP_UNREACH_NET_UNKNOWN = 6 ; unknown net |
ICMP_UNREACH_HOST_UNKNOWN = 7 ; unknown host |
ICMP_UNREACH_ISOLATED = 8 ; src host isolated |
ICMP_UNREACH_NET_PROHIB = 9 ; prohibited access |
ICMP_UNREACH_HOST_PROHIB = 10 ; ditto |
ICMP_UNREACH_TOSNET = 11 ; bad tos for net |
ICMP_UNREACH_TOSHOST = 12 ; bad tos for host |
ICMP_UNREACH_FILTER_PROHIB = 13 ; admin prohib |
ICMP_UNREACH_HOST_PRECEDENCE = 14 ; host prec vio. |
ICMP_UNREACH_PRECEDENCE_CUTOFF = 15 ; prec cutoff |
ICMP_SOURCEQUENCH = 4 ; Packet lost, slow down |
ICMP_REDIRECT = 5 ; shorter route, codes: |
ICMP_REDIRECT_NET = 0 ; for network |
ICMP_REDIRECT_HOST = 1 ; for host |
ICMP_REDIRECT_TOSNET = 2 ; for tos and net |
ICMP_REDIRECT_TOSHOST = 3 ; for tos and host |
ICMP_ALTHOSTADDR = 6 ; alternate host address |
ICMP_ECHO = 8 ; echo service |
ICMP_ROUTERADVERT = 9 ; router advertisement |
ICMP_ROUTERADVERT_NORMAL = 0 ; normal advertisement |
ICMP_ROUTERADVERT_NOROUTE_COMMON= 16 ; selective routing |
ICMP_ROUTERSOLICIT = 10 ; router solicitation |
ICMP_TIMXCEED = 11 ; time exceeded, code: |
ICMP_TIMXCEED_INTRANS = 0 ; ttl==0 in transit |
ICMP_TIMXCEED_REASS = 1 ; ttl==0 in reass |
ICMP_PARAMPROB = 12 ; ip header bad |
ICMP_PARAMPROB_ERRATPTR = 0 ; error at param ptr |
ICMP_PARAMPROB_OPTABSENT = 1 ; req. opt. absent |
ICMP_PARAMPROB_LENGTH = 2 ; bad length |
ICMP_TSTAMP = 13 ; timestamp request |
ICMP_TSTAMPREPLY = 14 ; timestamp reply |
ICMP_IREQ = 15 ; information request |
ICMP_IREQREPLY = 16 ; information reply |
ICMP_MASKREQ = 17 ; address mask request |
ICMP_MASKREPLY = 18 ; address mask reply |
ICMP_TRACEROUTE = 30 ; traceroute |
ICMP_DATACONVERR = 31 ; data conversion error |
ICMP_MOBILE_REDIRECT = 32 ; mobile host redirect |
ICMP_IPV6_WHEREAREYOU = 33 ; IPv6 where-are-you |
ICMP_IPV6_IAMHERE = 34 ; IPv6 i-am-here |
ICMP_MOBILE_REGREQUEST = 35 ; mobile registration req |
ICMP_MOBILE_REGREPLY = 36 ; mobile registreation reply |
ICMP_SKIP = 39 ; SKIP |
ICMP_PHOTURIS = 40 ; Photuris |
ICMP_PHOTURIS_UNKNOWN_INDEX = 1 ; unknown sec index |
ICMP_PHOTURIS_AUTH_FAILED = 2 ; auth failed |
ICMP_PHOTURIS_DECRYPT_FAILED = 3 ; decrypt failed |
struct ICMP_header |
Type db ? |
Code db ? |
Checksum dw ? |
Identifier dw ? |
SequenceNumber dw ? |
ends |
uglobal |
align 4 |
ICMP_PACKETS_TX rd NET_DEVICES_MAX |
ICMP_PACKETS_RX rd NET_DEVICES_MAX |
endg |
;----------------------------------------------------------------- |
; |
; ICMP_init |
; |
;----------------------------------------------------------------- |
macro ICMP_init { |
xor eax, eax |
mov edi, ICMP_PACKETS_TX |
mov ecx, 2*NET_DEVICES_MAX |
rep stosd |
} |
;----------------------------------------------------------------- |
; |
; ICMP_input: |
; |
; This procedure will send reply's to ICMP echo's |
; and insert packets into sockets when needed |
; |
; IN: Pointer to buffer in [esp] |
; size of buffer in [esp+4] |
; ebx = pointer to device struct |
; ecx = ICMP Packet size |
; esi = ptr to ICMP Packet data |
; edi = ptr to ipv4 source and dest address |
; |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
ICMP_input: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ICMP_input:\n" |
; First, check the checksum (altough some implementations ignore it) |
push esi ecx |
push [esi + ICMP_header.Checksum] |
mov [esi + ICMP_header.Checksum], 0 |
xor edx, edx |
call checksum_1 |
call checksum_2 |
pop si |
cmp dx, si |
pop ecx edx |
jne .checksum_mismatch |
; Check packet type |
cmp [edx + ICMP_header.Type], ICMP_ECHO ; Is this an echo request? |
jne .check_sockets |
; Update stats (and validate device ptr) |
call NET_ptr_to_num4 |
cmp edi, -1 |
je .dump |
inc [ICMP_PACKETS_RX + edi] |
; We well re-use the packet so we can create the response as fast as possible |
; Notice: this only works on pure ethernet |
DEBUGF DEBUG_NETWORK_VERBOSE, "got echo request\n" |
mov [edx + ICMP_header.Type], ICMP_ECHOREPLY ; Change Packet type to reply |
mov esi, [esp] ; Start of buffer |
cmp ebx, LOOPBACK_DEVICE |
je .loopback |
; FIXME: dont assume device is an ethernet device! |
; exchange dest and source address in IP header |
; exchange dest and source MAC in ETH header |
push dword [esi + ETH_header.DstMAC] |
push dword [esi + ETH_header.SrcMAC] |
pop dword [esi + ETH_header.DstMAC] |
pop dword [esi + ETH_header.SrcMAC] |
push word [esi + ETH_header.DstMAC + 4] |
push word [esi + ETH_header.SrcMAC + 4] |
pop word [esi + ETH_header.DstMAC + 4] |
pop word [esi + ETH_header.SrcMAC + 4] |
add esi, sizeof.ETH_header-4 |
.loopback: |
add esi, 4 |
push [esi + IPv4_header.SourceAddress] |
push [esi + IPv4_header.DestinationAddress] |
pop [esi + IPv4_header.SourceAddress] |
pop [esi + IPv4_header.DestinationAddress] |
; Recalculate ip header checksum |
movzx ecx, [esi + IPv4_header.VersionAndIHL] ; Calculate IP Header length by using IHL field |
and ecx, 0x0f |
shl cx, 2 |
mov edi, ecx ; IP header length |
mov eax, edx ; ICMP packet start addr |
push esi ; Calculate the IP checksum |
xor edx, edx ; |
call checksum_1 ; |
call checksum_2 ; |
pop esi ; |
mov [esi + IPv4_header.HeaderChecksum], dx ; |
; Recalculate ICMP CheckSum |
movzx ecx, [esi + IPv4_header.TotalLength] ; Find length of IP Packet |
xchg ch, cl ; |
sub ecx, edi ; IP packet length - IP header length = ICMP packet length |
mov esi, eax ; Calculate ICMP checksum |
xor edx, edx ; |
call checksum_1 ; |
call checksum_2 ; |
mov [eax + ICMP_header.Checksum], dx ; |
; Transmit the packet (notice that packet ptr and packet size have been on stack since start of the procedure!) |
call [ebx + NET_DEVICE.transmit] |
test eax, eax |
jnz @f |
call NET_ptr_to_num4 |
inc [ICMP_PACKETS_TX + edi] |
@@: |
ret |
.check_sockets: |
; Look for an open ICMP socket |
pusha |
mov ecx, socket_mutex |
call mutex_lock |
popa |
mov esi, [edi] ; ipv4 source address |
mov eax, net_sockets |
.try_more: |
; mov , [edx + ICMP_header.Identifier] |
.next_socket: |
mov eax, [eax + SOCKET.NextPtr] |
or eax, eax |
jz .dump_ |
cmp [eax + SOCKET.Domain], AF_INET4 |
jne .next_socket |
cmp [eax + SOCKET.Protocol], IP_PROTO_ICMP |
jne .next_socket |
cmp [eax + IP_SOCKET.RemoteIP], esi |
jne .next_socket |
; cmp [eax + ICMP_SOCKET.Identifier], |
; jne .next_socket |
; Update stats (and validate device ptr) |
call NET_ptr_to_num4 |
cmp edi, -1 |
je .dump_ |
inc [ICMP_PACKETS_RX + edi] |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "socket=%x\n", eax |
pusha |
lea ecx, [eax + SOCKET.mutex] |
call mutex_lock |
popa |
mov esi, edx |
jmp SOCKET_input |
.dump_: |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "ICMP_input: no socket found\n" |
jmp .dump |
.checksum_mismatch: |
DEBUGF DEBUG_NETWORK_VERBOSE, "checksum mismatch\n" |
.dump: |
DEBUGF DEBUG_NETWORK_VERBOSE, "ICMP_input: dumping\n" |
call NET_packet_free |
add esp, 4 ; pop (balance stack) |
ret |
if 0 |
;----------------------------------------------------------------- |
; |
; ICMP_output |
; |
; IN: eax = dest ip |
; bh = type |
; bl = code |
; ecx = data length |
; edx = source ip |
; esi = data offset |
; edi = identifier shl 16 + sequence number |
; |
;----------------------------------------------------------------- |
align 4 |
ICMP_output: |
DEBUGF DEBUG_NETWORK_VERBOSE, "Creating ICMP Packet\n" |
push esi edi bx |
add ecx, sizeof.ICMP_header |
mov di, IP_PROTO_ICMP SHL 8 + 128 ; TTL |
call IPv4_output |
jz .exit |
DEBUGF DEBUG_NETWORK_VERBOSE, "full icmp packet size: %u\n", edx |
pop word [edi + ICMP_header.Type] ; Write both type and code bytes at once |
pop dword [edi + ICMP_header.Identifier] ; identifier and sequence number |
mov [edi + ICMP_header.Checksum], 0 |
push ebx ecx edx |
mov esi, edi |
xor edx, edx |
call checksum_1 |
call checksum_2 |
mov [edi + ICMP_header.Checksum], dx |
pop edx ecx ebx esi |
sub ecx, sizeof.ICMP_header |
add edi, sizeof.ICMP_header |
push cx |
shr cx, 2 |
rep movsd |
pop cx |
and cx, 3 |
rep movsb |
sub edi, edx ;;; TODO: find a better way to remember start of packet |
push edx edi |
DEBUGF DEBUG_NETWORK_VERBOSE, "Sending ICMP Packet\n" |
call [ebx + NET_DEVICE.transmit] |
test eax, eax |
jnz @f |
call NET_ptr_to_num4 |
inc [ICMP_PACKETS_TX + edi] |
@@: |
ret |
.exit: |
DEBUGF DEBUG_NETWORK_ERROR, "Creating ICMP Packet failed\n" |
add esp, 2*4 + 2 |
ret |
end if |
;----------------------------------------------------------------- |
; |
; ICMP_output_raw |
; |
; IN: eax = socket ptr |
; ecx = data length |
; esi = data offset |
; |
;----------------------------------------------------------------- |
align 4 |
ICMP_output_raw: |
DEBUGF DEBUG_NETWORK_VERBOSE, "Creating ICMP Packet for socket %x, data ptr=%x\n", eax, edx |
push edx |
mov di, IP_PROTO_ICMP SHL 8 + 128 ; TTL |
mov edx, [eax + IP_SOCKET.LocalIP] |
mov eax, [eax + IP_SOCKET.RemoteIP] |
call IPv4_output |
jz .exit |
pop esi |
push edx |
push eax |
push edi ecx |
DEBUGF DEBUG_NETWORK_VERBOSE, "copying %u bytes from %x to %x\n", ecx, esi, edi |
rep movsb |
pop ecx edi |
mov [edi + ICMP_header.Checksum], 0 |
mov esi, edi |
xor edx, edx |
call checksum_1 |
call checksum_2 |
mov [edi + ICMP_header.Checksum], dx |
DEBUGF DEBUG_NETWORK_VERBOSE, "Sending ICMP Packet\n" |
call [ebx + NET_DEVICE.transmit] |
test eax, eax |
jnz @f |
call NET_ptr_to_num4 |
inc [ICMP_PACKETS_TX + edi] |
@@: |
ret |
.exit: |
DEBUGF DEBUG_NETWORK_ERROR, "Creating ICMP Packet failed\n" |
add esp, 4 |
ret |
;----------------------------------------------------------------- |
; |
; ICMP_API |
; |
; This function is called by system function 75 |
; |
; IN: subfunction number in bl |
; device number in bh |
; ecx, edx, .. depends on subfunction |
; |
; OUT: |
; |
;----------------------------------------------------------------- |
align 4 |
ICMP_api: |
movzx eax, bh |
shl eax, 2 |
test bl, bl |
jz .packets_tx ; 0 |
dec bl |
jz .packets_rx ; 1 |
.error: |
mov eax, -1 |
ret |
.packets_tx: |
mov eax, [ICMP_PACKETS_TX + eax] |
ret |
.packets_rx: |
mov eax, [ICMP_PACKETS_RX + eax] |
ret |
/kernel/branches/kolibri-process/network/loopback.inc |
---|
0,0 → 1,155 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; loopback.inc ;; |
;; ;; |
;; LoopBack device for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 2891 $ |
iglobal |
align 4 |
LOOPBACK_DEVICE: |
.device_type dd NET_DEVICE_LOOPBACK |
.mtu dd 4096 |
.name dd .namestr |
.unload dd .dummy_fn |
.reset dd .dummy_fn |
.transmit dd LOOP_input |
.bytes_tx dq 0 |
.bytes_rx dq 0 |
.packets_tx dd 0 |
.packets_rx dd 0 |
.link_state dd -1 |
.hwacc dd NET_HWACC_TCP_IPv4_IN + NET_HWACC_TCP_IPv4_OUT |
.namestr db 'loopback', 0 |
.dummy_fn: |
ret |
endg |
macro LOOP_init { |
local .fail |
mov ebx, LOOPBACK_DEVICE |
call NET_add_device |
cmp eax, -1 |
je .fail |
mov [IP_LIST], 127 + 1 shl 24 |
mov [SUBNET_LIST], 255 |
mov [BROADCAST_LIST], 0xffffff00 + 127 |
.fail: |
} |
;----------------------------------------------------------------- |
; |
; LOOP_input |
; |
; IN: [esp+4] = Pointer to buffer |
; [esp+8] = size of buffer |
; |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
LOOP_input: |
pop ebx |
pop eax |
pop ecx |
push ebx |
push ecx |
push eax |
inc [LOOPBACK_DEVICE.packets_rx] |
add dword[LOOPBACK_DEVICE.bytes_rx], ecx |
adc dword[LOOPBACK_DEVICE.bytes_rx + 4], 0 |
DEBUGF DEBUG_NETWORK_VERBOSE, "LOOP_input: size=%u\n", ecx |
lea edx, [eax + 4] |
mov eax, dword[eax] |
mov ebx, LOOPBACK_DEVICE |
cmp eax, AF_INET4 |
je IPv4_input |
DEBUGF DEBUG_NETWORK_VERBOSE, "LOOP_input: Unknown packet type=%x\n", ax |
.dump: |
DEBUGF DEBUG_NETWORK_VERBOSE, "LOOP_input: dumping\n" |
call NET_packet_free |
add esp, 4 |
ret |
;----------------------------------------------------------------- |
; |
; LOOP_output |
; |
; IN: |
; ecx = packet size |
; edi = address family |
; |
; OUT: edi = 0 on error, pointer to buffer otherwise |
; eax = buffer start |
; ebx = to device structure |
; ecx = unchanged (packet size of embedded data) |
; edx = size of complete buffer |
; |
;----------------------------------------------------------------- |
align 4 |
LOOP_output: |
DEBUGF DEBUG_NETWORK_VERBOSE, "LOOP_output\n" |
push ecx |
push edi |
add ecx, 4 |
cmp ecx, [LOOPBACK_DEVICE.mtu] |
ja .out_of_ram |
stdcall kernel_alloc, ecx |
test eax, eax |
jz .out_of_ram |
mov edi, eax |
pop eax |
stosd |
lea eax, [edi - 4] ; Set eax to buffer start |
pop ecx |
lea edx, [ecx + 4] ; Set edx to complete buffer size |
mov ebx, LOOPBACK_DEVICE |
inc [LOOPBACK_DEVICE.packets_tx] |
add dword[LOOPBACK_DEVICE.bytes_tx], ecx |
adc dword[LOOPBACK_DEVICE.bytes_tx + 4], 0 |
DEBUGF DEBUG_NETWORK_VERBOSE, "LOOP_output: ptr=%x size=%u\n", eax, edx |
ret |
.out_of_ram: |
DEBUGF DEBUG_NETWORK_ERROR, "LOOP_output: out of memory\n" |
add esp, 4+4 |
xor edi, edi |
ret |
/kernel/branches/kolibri-process/network/queue.inc |
---|
0,0 → 1,141 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2010. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; queue.inc ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 2305 $ |
; The Queues implemented by these macros form a ring-buffer. |
; The data to these queue's always looks like this: |
; |
; At top, you have the queue struct, wich has the size (number of currently queued packets, read and write pointers. |
; This struct is followed by a number of slots wich you can read and write to using the macros. |
; How these slots look like is up to you to chose, normally they should have at least a pointer to where the real data is. |
; (you can see some examples below) |
struct queue |
size dd ? ; number of queued packets in this queue |
w_ptr dd ? ; current writing pointer in queue |
r_ptr dd ? ; current reading pointer |
mutex MUTEX |
ends |
; The following macros share these inputs: |
; ptr = pointer to where the queue data is located |
; size = number of slots/entrys in the queue |
; entry_size = size of one slot, in bytes |
; failaddr = the address where macro will jump to when there is no data in the queue |
; additionally, add_to_queue requires you to set esi to the data wich you want to queue |
; get_from_queue on the other hand will return a pointer in esi, to the entry you're interessed in |
; PS: macros WILL destroy ecx and edi |
macro add_to_queue ptr, size, entry_size, failaddr { |
local .ok, .no_wrap |
pusha |
lea ecx, [ptr + queue.mutex] |
call mutex_lock |
popa |
cmp [ptr + queue.size], size ; Check if queue isnt full |
jb .ok |
pusha |
lea ecx, [ptr + queue.mutex] |
call mutex_unlock |
popa |
jmp failaddr |
.ok: |
inc [ptr + queue.size] ; if not full, queue one more |
mov edi, [ptr + queue.w_ptr] ; Current write pointer (FIFO!) |
mov ecx, entry_size/4 ; Write the queue entry |
rep movsd ; |
lea ecx, [size*entry_size+ptr+sizeof.queue] |
cmp edi, ecx ; entry size |
jb .no_wrap |
sub edi, size*entry_size |
.no_wrap: |
mov [ptr + queue.w_ptr], edi |
pusha |
lea ecx, [ptr + queue.mutex] |
call mutex_unlock |
popa |
} |
macro get_from_queue ptr, size, entry_size, failaddr { |
local .ok, .no_wrap |
pusha |
lea ecx, [ptr + queue.mutex] |
call mutex_lock |
popa |
cmp [ptr + queue.size], 0 ; any packets queued? |
ja .ok |
pusha |
lea ecx, [ptr + queue.mutex] |
call mutex_unlock |
popa |
jmp failaddr |
.ok: |
dec [ptr + queue.size] ; if so, dequeue one |
mov esi, [ptr + queue.r_ptr] |
push esi |
add esi, entry_size |
lea ecx, [size*entry_size+ptr+sizeof.queue] |
cmp esi, ecx ; entry size |
jb .no_wrap |
sub esi, size*entry_size |
.no_wrap: |
mov dword [ptr + queue.r_ptr], esi |
pop esi |
pusha |
lea ecx, [ptr + queue.mutex] |
call mutex_unlock |
popa |
} |
macro init_queue ptr { |
mov [ptr + queue.size] , 0 |
lea edi, [ptr + sizeof.queue] |
mov [ptr + queue.w_ptr], edi |
mov [ptr + queue.r_ptr], edi |
lea ecx, [ptr + queue.mutex] |
call mutex_init |
} |
/kernel/branches/kolibri-process/network/socket.inc |
---|
0,0 → 1,2406 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; Part of the TCP/IP network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org, ;; |
;; and Clevermouse. ;; |
;; ;; |
;; Based on code by mike.dld ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3514 $ |
struct SOCKET |
NextPtr dd ? ; pointer to next socket in list |
PrevPtr dd ? ; pointer to previous socket in list |
Number dd ? ; socket number |
mutex MUTEX |
PID dd ? ; process ID |
TID dd ? ; thread ID |
Domain dd ? ; INET/LOCAL/.. |
Type dd ? ; RAW/STREAM/DGRAM |
Protocol dd ? ; ICMP/IPv4/ARP/TCP/UDP |
errorcode dd ? |
device dd ? ; driver pointer, socket pointer if it's an LOCAL socket |
options dd ? |
state dd ? |
backlog dw ? ; how many incoming connections that can be queued |
snd_proc dd ? |
rcv_proc dd ? |
connect_proc dd ? |
ends |
struct IP_SOCKET SOCKET |
LocalIP rd 4 ; network byte order |
RemoteIP rd 4 ; network byte order |
ends |
struct TCP_SOCKET IP_SOCKET |
LocalPort dw ? ; network byte order |
RemotePort dw ? ; network byte order |
t_state dd ? ; TCB state |
t_rxtshift db ? |
rb 3 ; align |
t_rxtcur dd ? |
t_dupacks dd ? |
t_maxseg dd ? |
t_force dd ? |
t_flags dd ? |
;--------------- |
; RFC783 page 21 |
; send sequence |
SND_UNA dd ? ; sequence number of unack'ed sent Packets |
SND_NXT dd ? ; next send sequence number to use |
SND_UP dd ? ; urgent pointer |
SND_WL1 dd ? ; window minus one |
SND_WL2 dd ? ; |
ISS dd ? ; initial send sequence number |
SND_WND dd ? ; send window |
; receive sequence |
RCV_WND dd ? ; receive window |
RCV_NXT dd ? ; next receive sequence number to use |
RCV_UP dd ? ; urgent pointer |
IRS dd ? ; initial receive sequence number |
;--------------------- |
; Additional variables |
; receive variables |
RCV_ADV dd ? |
; retransmit variables |
SND_MAX dd ? |
; congestion control |
SND_CWND dd ? ; congestion window |
SND_SSTHRESH dd ? ; slow start threshold |
;---------------------- |
; Transmit timing stuff |
t_idle dd ? |
t_rtt dd ? |
t_rtseq dd ? |
t_srtt dd ? |
t_rttvar dd ? |
t_rttmin dd ? |
max_sndwnd dd ? |
;----------------- |
; Out-of-band data |
t_oobflags dd ? |
t_iobc dd ? |
t_softerror dd ? |
;--------- |
; RFC 1323 ; the order of next 4 elements may not change |
SND_SCALE db ? |
RCV_SCALE db ? |
requested_s_scale db ? |
request_r_scale db ? |
ts_recent dd ? ; a copy of the most-recent valid timestamp from the other end |
ts_recent_age dd ? |
last_ack_sent dd ? |
;------- |
; Timers |
timer_flags dd ? |
timer_retransmission dd ? ; rexmt |
timer_persist dd ? |
timer_keepalive dd ? ; keepalive/syn timeout |
timer_timed_wait dd ? ; also used as 2msl timer |
timer_connect dd ? |
; extra |
ts_ecr dd ? ; timestamp echo reply |
ts_val dd ? |
seg_next dd ? ; re-assembly queue |
ends |
struct UDP_SOCKET IP_SOCKET |
LocalPort dw ? ; network byte order |
RemotePort dw ? ; network byte order |
ends |
struct ICMP_SOCKET IP_SOCKET |
Identifier dw ? |
ends |
struct RING_BUFFER |
mutex MUTEX |
start_ptr dd ? ; Pointer to start of buffer |
end_ptr dd ? ; pointer to end of buffer |
read_ptr dd ? ; Read pointer |
write_ptr dd ? ; Write pointer |
size dd ? ; Number of bytes buffered |
ends |
struct STREAM_SOCKET TCP_SOCKET |
rcv RING_BUFFER |
snd RING_BUFFER |
ends |
struct socket_queue_entry |
data_ptr dd ? |
buf_ptr dd ? |
data_size dd ? |
ends |
SOCKETBUFFSIZE = 4096 ; in bytes |
SOCKET_QUEUE_SIZE = 10 ; maximum number of incoming packets queued for 1 socket |
; the incoming packet queue for sockets is placed in the socket struct itself, at this location from start |
SOCKET_QUEUE_LOCATION = (SOCKETBUFFSIZE - SOCKET_QUEUE_SIZE*sizeof.socket_queue_entry - sizeof.queue) |
uglobal |
align 4 |
net_sockets rd 4 |
last_socket_num dd ? |
last_UDP_port dw ? ; These values give the number of the last used ephemeral port |
last_TCP_port dw ? ; |
socket_mutex MUTEX |
endg |
;----------------------------------------------------------------- |
; |
; SOCKET_init |
; |
;----------------------------------------------------------------- |
macro SOCKET_init { |
xor eax, eax |
mov edi, net_sockets |
mov ecx, 5 |
rep stosd |
@@: |
pseudo_random eax |
cmp ax, EPHEMERAL_PORT_MIN |
jb @r |
cmp ax, EPHEMERAL_PORT_MAX |
ja @r |
xchg al, ah |
mov [last_UDP_port], ax |
@@: |
pseudo_random eax |
cmp ax, EPHEMERAL_PORT_MIN |
jb @r |
cmp ax, EPHEMERAL_PORT_MAX |
ja @r |
xchg al, ah |
mov [last_TCP_port], ax |
mov ecx, socket_mutex |
call mutex_init |
} |
;----------------------------------------------------------------- |
; |
; Socket API (function 74) |
; |
;----------------------------------------------------------------- |
align 4 |
sys_socket: |
mov dword[esp+20], 0 ; Set error code to 0 |
cmp ebx, 255 |
jz SOCKET_debug |
cmp ebx, .number |
ja .error |
jmp dword [.table + 4*ebx] |
.table: |
dd SOCKET_open ; 0 |
dd SOCKET_close ; 1 |
dd SOCKET_bind ; 2 |
dd SOCKET_listen ; 3 |
dd SOCKET_connect ; 4 |
dd SOCKET_accept ; 5 |
dd SOCKET_send ; 6 |
dd SOCKET_receive ; 7 |
dd SOCKET_set_opt ; 8 |
dd SOCKET_get_opt ; 9 |
dd SOCKET_pair ; 10 |
.number = ($ - .table) / 4 - 1 |
.error: |
mov dword[esp+32], -1 |
mov dword[esp+20], EINVAL |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_open |
; |
; IN: domain in ecx |
; type in edx |
; protocol in esi |
; OUT: eax is socket num, -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_open: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_open: domain=%u type=%u protocol=%x ", ecx, edx, esi |
push ecx edx esi |
call SOCKET_alloc |
pop esi edx ecx |
jz .nobuffs |
mov [esp+32], edi ; return socketnumber |
DEBUGF DEBUG_NETWORK_VERBOSE, "socknum=%u\n", edi |
test edx, SO_NONBLOCK |
jz @f |
or [eax + SOCKET.options], SO_NONBLOCK |
and edx, not SO_NONBLOCK |
@@: |
mov [eax + SOCKET.Domain], ecx |
mov [eax + SOCKET.Type], edx |
mov [eax + SOCKET.Protocol], esi |
mov [eax + SOCKET.connect_proc], connect_notsupp |
cmp ecx, AF_INET4 |
jne .no_inet4 |
cmp edx, SOCK_DGRAM |
je .udp |
cmp edx, SOCK_STREAM |
je .tcp |
cmp edx, SOCK_RAW |
je .raw |
.no_inet4: |
cmp ecx, AF_PPP |
jne .no_ppp |
cmp esi, PPP_PROTO_ETHERNET |
je .pppoe |
.no_ppp: |
.unsupported: |
push eax |
call SOCKET_free |
pop eax |
mov dword[esp+20], EOPNOTSUPP |
mov dword[esp+32], -1 |
ret |
.nobuffs: |
mov dword[esp+20], ENOBUFS |
mov dword[esp+32], -1 |
ret |
.raw: |
test esi, esi ; IP_PROTO_IP |
jz .raw_ip |
cmp esi, IP_PROTO_ICMP |
je .raw_icmp |
jmp .unsupported |
align 4 |
.udp: |
mov [eax + SOCKET.Protocol], IP_PROTO_UDP |
mov [eax + SOCKET.snd_proc], SOCKET_send_udp |
mov [eax + SOCKET.rcv_proc], SOCKET_receive_dgram |
mov [eax + SOCKET.connect_proc], UDP_connect |
ret |
align 4 |
.tcp: |
mov [eax + SOCKET.Protocol], IP_PROTO_TCP |
mov [eax + SOCKET.snd_proc], SOCKET_send_tcp |
mov [eax + SOCKET.rcv_proc], SOCKET_receive_stream |
mov [eax + SOCKET.connect_proc], TCP_connect |
TCP_init_socket eax |
ret |
align 4 |
.raw_ip: |
mov [eax + SOCKET.snd_proc], SOCKET_send_ip |
mov [eax + SOCKET.rcv_proc], SOCKET_receive_dgram |
mov [eax + SOCKET.connect_proc], IPv4_connect |
ret |
align 4 |
.raw_icmp: |
mov [eax + SOCKET.snd_proc], SOCKET_send_icmp |
mov [eax + SOCKET.rcv_proc], SOCKET_receive_dgram |
mov [eax + SOCKET.connect_proc], IPv4_connect |
ret |
align 4 |
.pppoe: |
push eax |
init_queue (eax + SOCKET_QUEUE_LOCATION) ; Set up data receiving queue |
pop eax |
mov [eax + SOCKET.snd_proc], SOCKET_send_pppoe |
mov [eax + SOCKET.rcv_proc], SOCKET_receive_dgram |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_bind |
; |
; IN: socket number in ecx |
; pointer to sockaddr struct in edx |
; length of that struct in esi |
; OUT: 0 on success |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_bind: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_bind: socknum=%u sockaddr=%x length=%u\n", ecx, edx, esi |
call SOCKET_num_to_ptr |
jz .invalid |
cmp esi, 2 |
jb .invalid |
cmp [eax + UDP_SOCKET.LocalPort], 0 ; Socket can only be bound once |
jnz .invalid |
cmp word [edx], AF_INET4 |
je .af_inet4 |
cmp word [edx], AF_LOCAL |
je .af_local |
.notsupp: |
mov dword[esp+20], EOPNOTSUPP |
mov dword[esp+32], -1 |
ret |
.invalid: |
mov dword[esp+20], EINVAL |
mov dword[esp+32], -1 |
ret |
.af_local: |
; TODO: write code here |
mov dword[esp+32], 0 |
ret |
.af_inet4: |
cmp esi, 6 |
jb .invalid |
cmp [eax + SOCKET.Protocol], IP_PROTO_UDP |
je .udp |
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP |
je .tcp |
jmp .notsupp |
.tcp: |
.udp: |
pushd [edx + 4] ; First, fill in the IP |
popd [eax + IP_SOCKET.LocalIP] |
mov bx, [edx + 2] ; Did caller specify a local port? |
test bx, bx |
jnz .just_check |
call SOCKET_find_port ; Nope, find an ephemeral one |
jmp .done |
.just_check: |
call SOCKET_check_port ; Yes, check if it's still available |
jz .addrinuse ; ZF is set by socket_check_port on error |
.done: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_bind: local ip=%u.%u.%u.%u\n",\ |
[eax + IP_SOCKET.LocalIP + 0]:1,[eax + IP_SOCKET.LocalIP + 1]:1,\ |
[eax + IP_SOCKET.LocalIP + 2]:1,[eax + IP_SOCKET.LocalIP + 3]:1 |
mov dword[esp+32], 0 |
ret |
.addrinuse: |
mov dword[esp+32], -1 |
mov dword[esp+20], EADDRINUSE |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_connect |
; |
; IN: socket number in ecx |
; pointer to sockaddr struct in edx |
; length of that struct in esi |
; OUT: 0 on success |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_connect: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_connect: socknum=%u sockaddr=%x length=%u\n", ecx, edx, esi |
call SOCKET_num_to_ptr |
jz .invalid |
cmp esi, 8 |
jb .invalid |
cmp [eax + SOCKET.state], SS_ISCONNECTING |
je .already |
test [eax + SOCKET.options], SO_ACCEPTCON |
jnz .notsupp |
call [eax + SOCKET.connect_proc] |
mov dword[esp+20], ebx |
mov dword[esp+32], eax |
ret |
.notsupp: |
mov dword[esp+20], EOPNOTSUPP |
mov dword[esp+32], -1 |
ret |
.invalid: |
mov dword[esp+20], EINVAL |
mov dword[esp+32], -1 |
ret |
.already: |
mov dword[esp+20], EALREADY |
mov dword[esp+32], -1 |
ret |
connect_notsupp: |
xor eax, eax |
dec eax |
mov ebx, EOPNOTSUPP |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_listen |
; |
; IN: socket number in ecx |
; backlog in edx |
; OUT: eax is socket num, -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_listen: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_listen: socknum=%u backlog=%u\n", ecx, edx |
call SOCKET_num_to_ptr |
jz .invalid |
cmp [eax + SOCKET.Domain], AF_INET4 |
jne .notsupp |
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP |
jne .invalid |
cmp [eax + TCP_SOCKET.LocalPort], 0 |
je .already |
cmp [eax + IP_SOCKET.LocalIP], 0 |
jne @f |
push [IP_LIST + 4] ;;; fixme!!!! |
pop [eax + IP_SOCKET.LocalIP] |
@@: |
cmp edx, MAX_backlog |
jbe @f |
mov edx, MAX_backlog |
@@: |
mov [eax + SOCKET.backlog], dx |
or [eax + SOCKET.options], SO_ACCEPTCON |
mov [eax + TCP_SOCKET.t_state], TCPS_LISTEN |
mov [eax + TCP_SOCKET.timer_keepalive], 0 ; disable keepalive timer |
push eax |
init_queue (eax + SOCKET_QUEUE_LOCATION) ; Set up sockets queue |
pop eax |
mov dword[esp+32], 0 |
ret |
.notsupp: |
mov dword[esp+20], EOPNOTSUPP |
mov dword[esp+32], -1 |
ret |
.invalid: |
mov dword[esp+20], EINVAL |
mov dword[esp+32], -1 |
ret |
.already: |
mov dword[esp+20], EALREADY |
mov dword[esp+32], -1 |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_accept |
; |
; IN: socket number in ecx |
; addr in edx |
; addrlen in esi |
; OUT: eax is socket num, -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_accept: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_accept: socknum=%u sockaddr=%x length=%u\n", ecx, edx, esi |
call SOCKET_num_to_ptr |
jz .invalid |
test [eax + SOCKET.options], SO_ACCEPTCON |
jz .invalid |
cmp [eax + SOCKET.Domain], AF_INET4 |
jne .notsupp |
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP |
jne .invalid |
.loop: |
get_from_queue (eax + SOCKET_QUEUE_LOCATION), MAX_backlog, 4, .block |
; Ok, we got a socket ptr |
mov eax, [esi] |
; Change thread ID to that of the current thread |
mov ebx, [TASK_BASE] |
mov ebx, [ebx + TASKDATA.pid] |
mov [eax + SOCKET.TID], ebx |
; Convert it to a socket number |
call SOCKET_ptr_to_num |
jz .invalid ; FIXME ? |
; and return it to caller |
mov [esp+32], eax |
ret |
.block: |
test [eax + SOCKET.options], SO_NONBLOCK |
jnz .wouldblock |
call SOCKET_block |
jmp .loop |
.wouldblock: |
mov dword[esp+20], EWOULDBLOCK |
mov dword[esp+32], -1 |
ret |
.invalid: |
mov dword[esp+20], EINVAL |
mov dword[esp+32], -1 |
ret |
.notsupp: |
mov dword[esp+20], EOPNOTSUPP |
mov dword[esp+32], -1 |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_close |
; |
; IN: socket number in ecx |
; OUT: eax is socket num, -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_close: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_close: socknum=%u\n", ecx |
call SOCKET_num_to_ptr |
jz .invalid |
mov dword[esp+32], 0 ; The socket exists, so we will succeed in closing it. |
or [eax + SOCKET.options], SO_NONBLOCK ; Mark the socket as non blocking, we dont want it to block any longer! |
test [eax + SOCKET.state], SS_BLOCKED ; Is the socket still in blocked state? |
jz @f |
call SOCKET_notify.unblock ; Unblock it. |
@@: |
cmp [eax + SOCKET.Domain], AF_INET4 |
jne .free |
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP |
je .tcp |
.free: |
call SOCKET_free |
ret |
.tcp: |
call TCP_usrclosed |
test eax, eax |
jz @f |
call TCP_output ; If connection is not closed yet, send the FIN |
@@: |
ret |
.invalid: |
mov dword[esp+20], EINVAL |
mov dword[esp+32], -1 |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_receive |
; |
; IN: socket number in ecx |
; addr to buffer in edx |
; length of buffer in esi |
; flags in edi |
; OUT: eax is number of bytes copied, -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_receive: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_receive: socknum=%u bufaddr=%x buflength=%u flags=%x\n", ecx, edx, esi, edi |
call SOCKET_num_to_ptr |
jz .invalid |
.loop: |
push edi |
call [eax + SOCKET.rcv_proc] |
pop edi |
test [eax + SOCKET.state], SS_CANTRCVMORE |
jnz .return |
cmp ebx, EWOULDBLOCK |
jne .return |
test edi, MSG_DONTWAIT |
jnz .return_err |
; test [eax + SOCKET.options], SO_NONBLOCK |
; jnz .return_err |
call SOCKET_block |
jmp .loop |
.invalid: |
push EINVAL |
pop ebx |
.return_err: |
mov ecx, -1 |
.return: |
mov [esp+20], ebx |
mov [esp+32], ecx |
ret |
align 4 |
SOCKET_receive_dgram: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_receive: DGRAM\n" |
mov ebx, esi ; bufferlength |
get_from_queue (eax + SOCKET_QUEUE_LOCATION), SOCKET_QUEUE_SIZE, sizeof.socket_queue_entry, .wouldblock ; sets esi only on success. |
mov ecx, [esi + socket_queue_entry.data_size] |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_receive: %u bytes data\n", ecx |
cmp ecx, ebx ; If data segment does not fit in applications buffer, abort |
ja .too_small |
push eax ecx |
push [esi + socket_queue_entry.buf_ptr] ; save the buffer addr so we can clear it later |
mov esi, [esi + socket_queue_entry.data_ptr] |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_receive: Source buffer=%x real addr=%x\n", [esp], esi |
; copy the data from kernel buffer to application buffer |
mov edi, edx ; bufferaddr |
shr ecx, 1 |
jnc .nb |
movsb |
.nb: |
shr ecx, 1 |
jnc .nw |
movsw |
.nw: |
test ecx, ecx |
jz .nd |
rep movsd |
.nd: |
call NET_packet_free |
pop ecx eax ; return number of bytes copied to application |
xor ebx, ebx |
ret |
.too_small: |
mov ecx, -1 |
push EMSGSIZE |
pop ebx |
ret |
.wouldblock: |
push EWOULDBLOCK |
pop ebx |
ret |
align 4 |
SOCKET_receive_local: |
; does this socket have a PID yet? |
cmp [eax + SOCKET.PID], 0 |
jne @f |
; Change PID to that of current process |
mov ebx, [TASK_BASE] |
mov ebx, [ebx + TASKDATA.pid] |
mov [eax + SOCKET.PID], ebx |
mov [eax + SOCKET.TID], ebx ; currently TID = PID in kolibrios :( |
@@: |
mov [eax + SOCKET.rcv_proc], SOCKET_receive_stream |
; ... continue to SOCKET_receive_stream |
align 4 |
SOCKET_receive_stream: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_receive: STREAM\n" |
cmp [eax + STREAM_SOCKET.rcv + RING_BUFFER.size], 0 |
je .wouldblock |
test edi, MSG_PEEK |
jnz .peek |
mov ecx, esi |
mov edi, edx |
xor edx, edx |
push eax |
add eax, STREAM_SOCKET.rcv |
call SOCKET_ring_read ; copy data from kernel buffer to application buffer |
call SOCKET_ring_free ; free read memory |
pop eax |
xor ebx, ebx ; errorcode = 0 (no error) |
ret |
.wouldblock: |
push EWOULDBLOCK |
pop ebx |
xor ecx, ecx |
ret |
.peek: |
mov ecx, [eax + STREAM_SOCKET.rcv + RING_BUFFER.size] |
xor ebx, ebx |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_send |
; |
; |
; IN: socket number in ecx |
; pointer to data in edx |
; datalength in esi |
; flags in edi |
; OUT: -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_send: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: socknum=%u data ptr=%x length=%u flags=%x\n", ecx, edx, esi, edi |
call SOCKET_num_to_ptr |
jz .invalid |
mov ecx, esi |
mov esi, edx |
jmp [eax + SOCKET.snd_proc] |
.invalid: |
mov dword[esp+20], EINVAL |
mov dword[esp+32], -1 |
ret |
align 4 |
SOCKET_send_udp: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: UDP\n" |
mov [esp+32], ecx |
call UDP_output |
cmp eax, -1 |
je .error |
ret |
.error: |
mov dword[esp+32], -1 |
mov dword[esp+20], EMSGSIZE ; FIXME: UDP_output should return error codes! |
ret |
align 4 |
SOCKET_send_tcp: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: TCP\n" |
push eax |
add eax, STREAM_SOCKET.snd |
call SOCKET_ring_write |
pop eax |
mov [esp+32], ecx |
mov [eax + SOCKET.errorcode], 0 |
push eax |
call TCP_output ; FIXME: this doesnt look pretty, does it? |
pop eax |
mov eax, [eax + SOCKET.errorcode] |
mov [esp+20], eax |
ret |
align 4 |
SOCKET_send_ip: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: IPv4\n" |
mov [esp+32], ecx |
call IPv4_output_raw ; FIXME: IPv4_output_raw should return error codes! |
cmp eax, -1 |
je .error |
ret |
.error: |
mov dword[esp+32], -1 |
mov dword[esp+20], EMSGSIZE |
ret |
align 4 |
SOCKET_send_icmp: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: ICMP\n" |
mov [esp+32], ecx |
call ICMP_output_raw ; FIXME: errorcodes |
cmp eax, -1 |
je .error |
ret |
.error: |
mov dword[esp+32], -1 |
mov dword[esp+20], EMSGSIZE |
ret |
align 4 |
SOCKET_send_pppoe: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: PPPoE\n" |
mov [esp+32], ecx |
mov ebx, [eax + SOCKET.device] |
call PPPoE_discovery_output ; FIXME: errorcodes |
cmp eax, -1 |
je .error |
ret |
.error: |
mov dword[esp+32], -1 |
mov dword[esp+20], EMSGSIZE |
ret |
align 4 |
SOCKET_send_local: |
; does this socket have a PID yet? |
cmp [eax + SOCKET.PID], 0 |
jne @f |
; Change PID to that of current process |
mov ebx, [TASK_BASE] |
mov ebx, [ebx + TASKDATA.pid] |
mov [eax + SOCKET.PID], ebx |
mov [eax + SOCKET.TID], ebx ; currently TID = PID in kolibrios :( |
@@: |
mov [eax + SOCKET.snd_proc], SOCKET_send_local_ |
align 4 |
SOCKET_send_local_: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: LOCAL\n" |
; get the other side's socket and check if it still exists |
mov eax, [eax + SOCKET.device] |
call SOCKET_check |
jz .invalid |
; allright, shove in the data! |
push eax |
add eax, STREAM_SOCKET.rcv |
call SOCKET_ring_write |
pop eax |
; return the number of written bytes (or errorcode) to application |
mov [esp+32], ecx |
; and notify the other end |
call SOCKET_notify |
ret |
.invalid: |
mov dword[esp+32], -1 |
mov dword[esp+20], EINVAL |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_get_options |
; |
; IN: ecx = socket number |
; edx = pointer to the options: |
; dd level, optname, optval, optlen |
; OUT: -1 on error |
; |
; At moment, uses only pseudo-optname -2 for get last_ack_number for TCP. |
; TODO: find best way to notify that send()'ed data were acknowledged |
; Also pseudo-optname -3 is valid and returns socket state, one of TCPS_*. |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_get_opt: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_get_opt\n" |
call SOCKET_num_to_ptr |
jz .invalid |
cmp dword [edx], IP_PROTO_TCP |
jne .invalid |
cmp dword [edx+4], -2 |
je @f |
cmp dword [edx+4], -3 |
jne .invalid |
@@: |
; mov eax, [edx+12] |
; test eax, eax |
; jz .fail |
; cmp dword [eax], 4 |
; mov dword [eax], 4 |
; jb .fail |
; stdcall net_socket_num_to_addr, ecx |
; test eax, eax |
; jz .fail |
; ; todo: check that eax is really TCP socket |
; mov ecx, [eax + TCP_SOCKET.last_ack_number] |
; cmp dword [edx+4], -2 |
; jz @f |
; mov ecx, [eax + TCP_SOCKET.state] |
@@: |
mov eax, [edx+8] |
test eax, eax |
jz @f |
mov [eax], ecx |
@@: |
mov dword [esp+32], 0 |
ret |
.invalid: |
mov dword[esp+32], -1 |
mov dword[esp+20], EINVAL |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_set_options |
; |
; IN: ecx = socket number |
; edx = pointer to the options: |
; dd level, optname, optlen, optval |
; OUT: -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_set_opt: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_set_opt\n" |
call SOCKET_num_to_ptr |
jz .invalid |
cmp dword [edx], SOL_SOCKET |
jne .invalid |
cmp dword [edx+4], SO_BINDTODEVICE |
je .bind |
.invalid: |
mov dword[esp+32], -1 |
mov dword[esp+20], EINVAL |
ret |
.bind: |
cmp dword[edx+8], 0 |
je .unbind |
movzx edx, byte[edx + 9] |
cmp edx, NET_DEVICES_MAX |
ja .invalid |
mov edx, [NET_DRV_LIST + 4*edx] |
test edx, edx |
jz .already |
mov [eax + SOCKET.device], edx |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_set_opt: Bound socket %x to device %x\n",eax, edx |
mov dword[esp+32], 0 ; success! |
ret |
.unbind: |
mov [eax + SOCKET.device], 0 |
mov dword[esp+32], 0 ; success! |
ret |
.already: |
mov dword[esp+20], EALREADY |
mov dword[esp+32], -1 |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_pair |
; |
; Allocates a pair of linked LOCAL domain sockets |
; |
; IN: / |
; OUT: eax is socket1 num, -1 on error |
; ebx is socket2 num |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_pair: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_pair\n" |
call SOCKET_alloc |
jz .nomem1 |
mov [esp+32], edi ; application's eax |
mov [eax + SOCKET.Domain], AF_LOCAL |
mov [eax + SOCKET.Type], SOCK_STREAM |
mov [eax + SOCKET.Protocol], 0 ;;; CHECKME |
mov [eax + SOCKET.snd_proc], SOCKET_send_local |
mov [eax + SOCKET.rcv_proc], SOCKET_receive_local |
mov [eax + SOCKET.PID], 0 |
mov ebx, eax |
call SOCKET_alloc |
jz .nomem2 |
mov [esp+20], edi ; application's ebx |
mov [eax + SOCKET.Domain], AF_LOCAL |
mov [eax + SOCKET.Type], SOCK_STREAM |
mov [eax + SOCKET.Protocol], 0 ;;; CHECKME |
mov [eax + SOCKET.snd_proc], SOCKET_send_local |
mov [eax + SOCKET.rcv_proc], SOCKET_receive_local |
mov [eax + SOCKET.PID], 0 |
; Link the two sockets to eachother |
mov [eax + SOCKET.device], ebx |
mov [ebx + SOCKET.device], eax |
lea eax, [eax + STREAM_SOCKET.rcv] |
call SOCKET_ring_create |
lea eax, [ebx + STREAM_SOCKET.rcv] |
call SOCKET_ring_create |
pop eax |
ret |
.nomem2: |
mov eax, ebx |
call SOCKET_free |
.nomem1: |
mov dword[esp+32], -1 |
mov dword[esp+28], ENOMEM |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_debug |
; |
; Copies socket variables to application buffer |
; |
; IN: ecx = socket number |
; edx = pointer to buffer |
; |
; OUT: -1 on error |
;----------------------------------------------------------------- |
align 4 |
SOCKET_debug: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_debug\n" |
mov edi, edx |
test ecx, ecx |
jz .returnall |
call SOCKET_num_to_ptr |
jz .invalid |
mov esi, eax |
mov ecx, SOCKETBUFFSIZE/4 |
rep movsd |
mov dword[esp+32], 0 |
ret |
.returnall: |
mov ebx, net_sockets |
.next_socket: |
mov ebx, [ebx + SOCKET.NextPtr] |
test ebx, ebx |
jz .done |
mov eax, [ebx + SOCKET.Number] |
stosd |
jmp .next_socket |
.done: |
xor eax, eax |
stosd |
mov dword[esp+32], eax |
ret |
.invalid: |
mov dword[esp+32], -1 |
mov dword[esp+28], EINVAL |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_find_port |
; |
; Fills in the local port number for TCP and UDP sockets |
; This procedure always works because the number of sockets is |
; limited to a smaller number then the number of possible ports |
; |
; IN: eax = socket pointer |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_find_port: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_find_port\n" |
push ebx esi ecx |
cmp [eax + SOCKET.Protocol], IP_PROTO_UDP |
je .udp |
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP |
je .tcp |
pop ecx esi ebx |
ret |
.udp: |
mov bx, [last_UDP_port] |
call .findit |
mov [last_UDP_port], bx |
pop ecx esi ebx |
ret |
.tcp: |
mov bx, [last_TCP_port] |
call .findit |
mov [last_TCP_port], bx |
pop ecx esi ebx |
ret |
.restart: |
mov bx, MIN_EPHEMERAL_PORT_N |
.findit: |
cmp bx, MAX_EPHEMERAL_PORT_N |
je .restart |
add bh, 1 |
adc bl, 0 |
call SOCKET_check_port |
jz .findit |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_check_port (to be used with AF_INET only!) |
; |
; Checks if a local port number is unused |
; If the proposed port number is unused, it is filled in in the socket structure |
; |
; IN: eax = socket ptr (to find out if its a TCP/UDP socket) |
; bx = proposed socket number (network byte order) |
; |
; OUT: ZF = set on error |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_check_port: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_check_port: " |
pusha |
mov ecx, socket_mutex |
call mutex_lock |
popa |
mov ecx, [eax + SOCKET.Protocol] |
mov edx, [eax + IP_SOCKET.LocalIP] |
mov esi, net_sockets |
.next_socket: |
mov esi, [esi + SOCKET.NextPtr] |
or esi, esi |
jz .port_ok |
cmp [esi + SOCKET.Protocol], ecx |
jne .next_socket |
cmp [esi + IP_SOCKET.LocalIP], edx |
jne .next_socket |
cmp [esi + UDP_SOCKET.LocalPort], bx |
jne .next_socket |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "local port %x already in use\n", bx ; FIXME: find a way to print big endian values with debugf |
ret |
.port_ok: |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "local port %x is free\n", bx ; FIXME: find a way to print big endian values with debugf |
mov [eax + UDP_SOCKET.LocalPort], bx |
or bx, bx ; clear the zero-flag |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_input |
; |
; Updates a (stateless) socket with received data |
; |
; Note: the mutex should already be set ! |
; |
; IN: eax = socket ptr |
; ecx = data size |
; esi = ptr to data |
; [esp] = ptr to buf |
; [esp + 4] = buf size |
; |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_input: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_input: socket=%x, data=%x size=%u\n", eax, esi, ecx |
mov [esp+4], ecx |
push esi |
mov esi, esp |
add_to_queue (eax + SOCKET_QUEUE_LOCATION), SOCKET_QUEUE_SIZE, sizeof.socket_queue_entry, SOCKET_input.full |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_input: success\n" |
add esp, sizeof.socket_queue_entry |
pusha |
lea ecx, [eax + SOCKET.mutex] |
call mutex_unlock |
popa |
jmp SOCKET_notify |
.full: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_input: socket %x is full!\n", eax |
pusha |
lea ecx, [eax + SOCKET.mutex] |
call mutex_unlock |
popa |
call NET_packet_free |
add esp, 8 |
ret |
;-------------------------- |
; |
; eax = ptr to ring struct (just a buffer of the right size) |
; |
align 4 |
SOCKET_ring_create: |
push esi |
mov esi, eax |
push edx |
stdcall create_ring_buffer, SOCKET_MAXDATA, PG_SW |
pop edx |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ring_created: %x\n", eax |
pusha |
lea ecx, [esi + RING_BUFFER.mutex] |
call mutex_init |
popa |
mov [esi + RING_BUFFER.start_ptr], eax |
mov [esi + RING_BUFFER.write_ptr], eax |
mov [esi + RING_BUFFER.read_ptr], eax |
mov [esi + RING_BUFFER.size], 0 |
add eax, SOCKET_MAXDATA |
mov [esi + RING_BUFFER.end_ptr], eax |
mov eax, esi |
pop esi |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_ring_write |
; |
; Adds data to a stream socket, and updates write pointer and size |
; |
; IN: eax = ptr to ring struct |
; ecx = data size |
; esi = ptr to data |
; |
; OUT: ecx = number of bytes stored |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_ring_write: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ring_write: ringbuff=%x ptr=%x size=%u\n", eax, esi, ecx |
; lock mutex |
pusha |
lea ecx, [eax + RING_BUFFER.mutex] |
call mutex_lock ; TODO: check what registers this function actually destroys |
popa |
; calculate available size |
mov edi, SOCKET_MAXDATA |
sub edi, [eax + RING_BUFFER.size] ; available buffer size in edi |
cmp ecx, edi |
jbe .copy |
mov ecx, edi |
.copy: |
mov edi, [eax + RING_BUFFER.write_ptr] |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ring_write: %u bytes from %x to %x\n", ecx, esi, edi |
; update write ptr |
push edi |
add edi, ecx |
cmp edi, [eax + RING_BUFFER.end_ptr] |
jb @f |
sub edi, SOCKET_MAXDATA ; WRAP |
@@: |
mov [eax + RING_BUFFER.write_ptr], edi |
pop edi |
; update size |
add [eax + RING_BUFFER.size], ecx |
; copy the data |
push ecx |
shr ecx, 1 |
jnc .nb |
movsb |
.nb: |
shr ecx, 1 |
jnc .nw |
movsw |
.nw: |
test ecx, ecx |
jz .nd |
rep movsd |
.nd: |
pop ecx |
; unlock mutex |
pusha |
lea ecx, [eax + RING_BUFFER.mutex] |
call mutex_unlock ; TODO: check what registers this function actually destroys |
popa |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_ring_read |
; |
; IN: eax = ring struct ptr |
; ecx = bytes to read |
; edx = offset |
; edi = ptr to buffer start |
; |
; OUT: eax = unchanged |
; ecx = number of bytes read (0 on error) |
; edx = destroyed |
; esi = destroyed |
; edi = ptr to buffer end |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_ring_read: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ring_read: ringbuff=%x ptr=%x size=%u offset=%x\n", eax, edi, ecx, edx |
pusha |
lea ecx, [eax + RING_BUFFER.mutex] |
call mutex_lock ; TODO: check what registers this function actually destroys |
popa |
mov esi, [eax + RING_BUFFER.read_ptr] |
add esi, edx ; esi = start_ptr + offset |
neg edx |
add edx, [eax + RING_BUFFER.size] ; edx = snd.size - offset |
jle .no_data_at_all |
pusha |
lea ecx, [eax + RING_BUFFER.mutex] |
call mutex_unlock ; TODO: check what registers this function actually destroys |
popa |
cmp ecx, edx |
ja .less_data |
.copy: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ring_read: %u bytes from %x to %x\n", ecx, esi, edi |
push ecx |
shr ecx, 1 |
jnc .nb |
movsb |
.nb: |
shr ecx, 1 |
jnc .nw |
movsw |
.nw: |
test ecx, ecx |
jz .nd |
rep movsd |
.nd: |
pop ecx |
ret |
.no_data_at_all: |
pusha |
lea ecx, [eax + RING_BUFFER.mutex] |
call mutex_unlock ; TODO: check what registers this function actually destroys |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ring_read: no data at all!\n" |
xor ecx, ecx |
ret |
.less_data: |
mov ecx, edx |
jmp .copy |
;----------------------------------------------------------------- |
; |
; SOCKET_ring_free |
; |
; Free's some bytes from the ringbuffer |
; |
; IN: eax = ptr to ring struct |
; ecx = data size |
; |
; OUT: ecx = number of bytes free-ed |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_ring_free: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ring_free: %u bytes from ring %x\n", ecx, eax |
push eax ecx |
lea ecx, [eax + RING_BUFFER.mutex] |
call mutex_lock ; TODO: check what registers this function actually destroys |
pop ecx eax |
sub [eax + RING_BUFFER.size], ecx |
jb .error |
add [eax + RING_BUFFER.read_ptr], ecx |
mov edx, [eax + RING_BUFFER.end_ptr] |
cmp [eax + RING_BUFFER.read_ptr], edx |
jb @f |
sub [eax + RING_BUFFER.read_ptr], SOCKET_MAXDATA |
@@: |
push eax ecx |
lea ecx, [eax + RING_BUFFER.mutex] ; TODO: check what registers this function actually destroys |
call mutex_unlock |
pop ecx eax |
ret |
.error: ; we could free all available bytes, but that would be stupid, i guess.. |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ring_free: buffer=%x error!\n", eax |
add [eax + RING_BUFFER.size], ecx |
push eax |
lea ecx, [eax + RING_BUFFER.mutex] |
call mutex_unlock ; TODO: check what registers this function actually destroys |
pop eax |
xor ecx, ecx |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_block |
; |
; Suspends the thread attached to a socket |
; |
; IN: eax = socket ptr |
; OUT: eax = unchanged |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_block: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_block: %x\n", eax |
pushf |
push eax |
cli |
; Set the 'socket is blocked' flag |
or [eax + SOCKET.state], SS_BLOCKED |
; Suspend the thread |
push edx |
mov edx, [TASK_BASE] |
mov [edx + TASKDATA.state], 1 ; Suspended |
; Remember the thread ID so we can wake it up again |
mov edx, [edx + TASKDATA.pid] |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_block: suspending thread: %u\n", edx |
mov [eax + SOCKET.TID], edx |
pop edx |
call change_task |
pop eax |
popf |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_block: continueing\n" |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_notify |
; |
; notify's the owner of a socket that something happened |
; |
; IN: eax = socket ptr |
; OUT: eax = unchanged |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_notify: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_notify: %x\n", eax |
call SOCKET_check |
jz .error |
test [eax + SOCKET.state], SS_BLOCKED |
jnz .unblock |
; test [eax + SOCKET.options], SO_NONBLOCK |
; jz .error |
push eax ecx esi |
; socket exists and is of non blocking type. |
; We'll try to flag an event to the thread |
mov eax, [eax + SOCKET.TID] |
test eax, eax |
jz .done |
mov ecx, 1 |
mov esi, TASK_DATA + TASKDATA.pid |
.next_pid: |
cmp [esi], eax |
je .found_pid |
inc ecx |
add esi, 0x20 |
cmp ecx, [TASK_COUNT] |
jbe .next_pid |
; PID not found, TODO: close socket! |
jmp .done |
.found_pid: |
shl ecx, 8 |
or [ecx + SLOT_BASE + APPDATA.event_mask], EVENT_NETWORK |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_notify: poking thread %u!\n", eax |
jmp .done |
.unblock: |
push eax ecx esi |
; Clear the 'socket is blocked' flag |
and [eax + SOCKET.state], not SS_BLOCKED |
; Find the thread's TASK_DATA |
mov eax, [eax + SOCKET.TID] |
test eax, eax |
jz .error |
xor ecx, ecx |
inc ecx |
mov esi, TASK_DATA |
.next: |
cmp [esi + TASKDATA.pid], eax |
je .found |
inc ecx |
add esi, 0x20 |
cmp ecx, [TASK_COUNT] |
jbe .next |
jmp .error |
.found: |
; Run the thread |
mov [esi + TASKDATA.state], 0 ; Running |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_notify: Unblocked socket!\n" |
.done: |
pop esi ecx eax |
.error: |
ret |
;-------------------------------------------------------------------- |
; |
; SOCKET_alloc |
; |
; Allocate memory for socket data and put new socket into the list |
; Newly created socket is initialized with calling PID and number and |
; put into beginning of list (which is a fastest way). |
; |
; IN: / |
; OUT: eax = 0 on error, socket ptr otherwise |
; edi = socket number |
; ZF = cleared on error |
; |
;-------------------------------------------------------------------- |
align 4 |
SOCKET_alloc: |
push ebx |
stdcall kernel_alloc, SOCKETBUFFSIZE |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_alloc: ptr=%x\n", eax |
or eax, eax |
jz .exit |
; zero-initialize allocated memory |
push eax |
mov edi, eax |
mov ecx, SOCKETBUFFSIZE / 4 |
xor eax, eax |
rep stosd |
pop eax |
; set send-and receive procedures to return -1 |
mov [eax + SOCKET.snd_proc], .not_yet |
mov [eax + SOCKET.rcv_proc], .not_yet |
pusha |
mov ecx, socket_mutex |
call mutex_lock |
popa |
; find first free socket number and use it |
mov edi, [last_socket_num] |
.next_socket_number: |
inc edi |
jz .next_socket_number ; avoid socket nr 0 |
cmp edi, -1 |
je .next_socket_number ; avoid socket nr -1 |
mov ebx, net_sockets |
.next_socket: |
mov ebx, [ebx + SOCKET.NextPtr] |
test ebx, ebx |
jz .last_socket |
cmp [ebx + SOCKET.Number], edi |
jne .next_socket |
jmp .next_socket_number |
.last_socket: |
mov [last_socket_num], edi |
mov [eax + SOCKET.Number], edi |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_alloc: number=%u\n", edi |
; Fill in PID |
mov ebx, [TASK_BASE] |
mov ebx, [ebx + TASKDATA.pid] |
mov [eax + SOCKET.PID], ebx |
mov [eax + SOCKET.TID], ebx ; currently TID = PID in kolibrios :( |
; init mutex |
pusha |
lea ecx, [eax + SOCKET.mutex] |
call mutex_init |
popa |
; add socket to the list by re-arranging some pointers |
mov ebx, [net_sockets + SOCKET.NextPtr] |
mov [eax + SOCKET.PrevPtr], net_sockets |
mov [eax + SOCKET.NextPtr], ebx |
test ebx, ebx |
jz @f |
pusha |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_lock |
popa |
mov [ebx + SOCKET.PrevPtr], eax |
pusha |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
popa |
@@: |
mov [net_sockets + SOCKET.NextPtr], eax |
or eax, eax ; used to clear zero flag |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
.exit: |
pop ebx |
ret |
.not_yet: |
mov dword[esp+20], ENOTCONN |
mov dword[esp+32], -1 |
ret |
;---------------------------------------------------- |
; |
; SOCKET_free |
; |
; Free socket data memory and remove socket from the list |
; Caller should lock and unlock socket_mutex |
; |
; IN: eax = socket ptr |
; OUT: / |
; |
;---------------------------------------------------- |
align 4 |
SOCKET_free: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_free: %x\n", eax |
call SOCKET_check |
jz .error |
push ebx |
pusha |
lea ecx, [eax + SOCKET.mutex] |
call mutex_lock |
popa |
cmp [eax + SOCKET.Domain], AF_INET4 |
jnz .no_tcp |
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP |
jnz .no_tcp |
mov ebx, eax |
stdcall kernel_free, [ebx + STREAM_SOCKET.rcv.start_ptr] |
stdcall kernel_free, [ebx + STREAM_SOCKET.snd.start_ptr] |
mov eax, ebx |
.no_tcp: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_free: freeing socket %x\n", eax |
push eax ; this will be passed to kernel_free |
mov ebx, [eax + SOCKET.NextPtr] |
mov eax, [eax + SOCKET.PrevPtr] |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_free: linking socket %x to socket %x\n", eax, ebx |
test eax, eax |
jz @f |
mov [eax + SOCKET.NextPtr], ebx |
@@: |
test ebx, ebx |
jz @f |
mov [ebx + SOCKET.PrevPtr], eax |
@@: |
call kernel_free |
pop ebx |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_free: success!\n" |
.error: |
ret |
;------------------------------------ |
; |
; SOCKET_fork |
; |
; Create a child socket |
; |
; IN: socket nr in ebx |
; OUT: child socket nr in eax |
; |
;----------------------------------- |
align 4 |
SOCKET_fork: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_fork: %x\n", ebx |
; Exit if backlog queue is full |
mov eax, [ebx + SOCKET_QUEUE_LOCATION + queue.size] |
cmp ax, [ebx + SOCKET.backlog] |
jae .fail |
; Allocate new socket |
push ebx |
call SOCKET_alloc |
pop ebx |
jz .fail |
push eax |
mov esi, esp |
add_to_queue (ebx + SOCKET_QUEUE_LOCATION), MAX_backlog, 4, .fail2 |
pop eax |
; Copy structure from current socket to new |
; We start at PID to preserve the socket num, 2 pointers and mutex |
; TID will be filled in later |
lea esi, [ebx + SOCKET.PID] |
lea edi, [eax + SOCKET.PID] |
mov ecx, (SOCKET_QUEUE_LOCATION - SOCKET.PID + 3)/4 |
rep movsd |
and [eax + SOCKET.options], not SO_ACCEPTCON |
; Notify owner of parent socket |
push eax |
mov eax, ebx |
call SOCKET_notify |
pop eax |
ret |
.fail2: |
add esp, 4+4+4 |
.fail: |
DEBUGF DEBUG_NETWORK_ERROR, "SOCKET_fork: failed\n" |
xor eax, eax |
ret |
;--------------------------------------------------- |
; |
; SOCKET_num_to_ptr |
; |
; Get socket structure address by its number |
; |
; IN: ecx = socket number |
; OUT: eax = 0 on error, socket ptr otherwise |
; ZF = set on error |
; |
;--------------------------------------------------- |
align 4 |
SOCKET_num_to_ptr: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_num_to_ptr: num=%u ", ecx |
pusha |
mov ecx, socket_mutex |
call mutex_lock |
popa |
mov eax, net_sockets |
.next_socket: |
mov eax, [eax + SOCKET.NextPtr] |
or eax, eax |
jz .error |
cmp [eax + SOCKET.Number], ecx |
jne .next_socket |
test eax, eax |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "ptr=%x\n", eax |
ret |
.error: |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_ERROR, "SOCKET_num_to_ptr: not found\n", eax |
ret |
;--------------------------------------------------- |
; |
; SOCKET_ptr_to_num |
; |
; Get socket number by its address |
; |
; IN: eax = socket ptr |
; OUT: eax = 0 on error, socket num otherwise |
; ZF = set on error |
; |
;--------------------------------------------------- |
align 4 |
SOCKET_ptr_to_num: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_ptr_to_num: ptr=%x ", eax |
call SOCKET_check |
jz .error |
mov eax, [eax + SOCKET.Number] |
DEBUGF DEBUG_NETWORK_VERBOSE, "num=%u\n", eax |
ret |
.error: |
DEBUGF DEBUG_NETWORK_ERROR, "SOCKET_ptr_to_num: not found\n", eax |
ret |
;--------------------------------------------------- |
; |
; SOCKET_check |
; |
; checks if the given value is really a socket ptr |
; |
; IN: eax = socket ptr |
; OUT: eax = 0 on error, unchanged otherwise |
; ZF = set on error |
; |
;--------------------------------------------------- |
align 4 |
SOCKET_check: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_check: %x\n", eax |
push ebx |
mov ebx, net_sockets |
.next_socket: |
mov ebx, [ebx + SOCKET.NextPtr] |
or ebx, ebx |
jz .done |
cmp ebx, eax |
jnz .next_socket |
.done: |
mov eax, ebx |
test eax, eax |
pop ebx |
ret |
;--------------------------------------------------- |
; |
; SOCKET_check_owner |
; |
; checks if the caller application owns the socket |
; |
; IN: eax = socket ptr |
; OUT: ZF = true/false |
; |
;--------------------------------------------------- |
align 4 |
SOCKET_check_owner: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_check_owner: %x\n", eax |
push ebx |
mov ebx, [TASK_BASE] |
mov ebx, [ebx + TASKDATA.pid] |
cmp [eax + SOCKET.PID], ebx |
pop ebx |
ret |
;------------------------------------------------------ |
; |
; SOCKET_process_end |
; |
; Kernel calls this function when a certain process ends |
; This function will check if the process had any open sockets |
; And update them accordingly (clean up) |
; |
; IN: edx = pid |
; OUT: / |
; |
;------------------------------------------------------ |
align 4 |
SOCKET_process_end: |
cmp [net_sockets + SOCKET.NextPtr], 0 ; Are there any active sockets at all? |
je .quickret ; nope, exit immediately |
; TODO: run the following code in another thread, to avoid deadlock |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_process_end: %x\n", edx |
pusha |
mov ecx, socket_mutex |
call mutex_lock |
popa |
push ebx |
mov ebx, net_sockets |
.next_socket: |
mov ebx, [ebx + SOCKET.NextPtr] |
.next_socket_test: |
test ebx, ebx |
jz .done |
cmp [ebx + SOCKET.PID], edx |
jne .next_socket |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_process_end: killing socket %x\n", ebx |
mov [ebx + SOCKET.PID], 0 |
mov eax, ebx |
mov ebx, [ebx + SOCKET.NextPtr] |
pusha |
cmp [eax + SOCKET.Domain], AF_INET4 |
jne .free |
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP |
jne .free |
call TCP_disconnect |
jmp .closed |
.free: |
call SOCKET_free |
.closed: |
popa |
jmp .next_socket_test |
.done: |
pop ebx |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
.quickret: |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_is_connecting |
; |
; IN: eax = socket ptr |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_is_connecting: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_is_connecting: %x\n", eax |
and [eax + SOCKET.state], not (SS_ISCONNECTED + SS_ISDISCONNECTING + SS_ISCONFIRMING) |
or [eax + SOCKET.state], SS_ISCONNECTING |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_is_connected |
; |
; IN: eax = socket ptr |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_is_connected: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_is_connected: %x\n", eax |
and [eax + SOCKET.state], not (SS_ISCONNECTING + SS_ISDISCONNECTING + SS_ISCONFIRMING) |
or [eax + SOCKET.state], SS_ISCONNECTED |
jmp SOCKET_notify |
;----------------------------------------------------------------- |
; |
; SOCKET_is_disconnecting |
; |
; IN: eax = socket ptr |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_is_disconnecting: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_is_disconnecting: %x\n", eax |
and [eax + SOCKET.state], not (SS_ISCONNECTING) |
or [eax + SOCKET.state], SS_ISDISCONNECTING + SS_CANTRCVMORE + SS_CANTSENDMORE |
jmp SOCKET_notify |
;----------------------------------------------------------------- |
; |
; SOCKET_is_disconnected |
; |
; IN: eax = socket ptr |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_is_disconnected: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_is_disconnected: %x\n", eax |
and [eax + SOCKET.state], not (SS_ISCONNECTING + SS_ISCONNECTED + SS_ISDISCONNECTING) |
or [eax + SOCKET.state], SS_CANTRCVMORE + SS_CANTSENDMORE |
jmp SOCKET_notify |
;----------------------------------------------------------------- |
; |
; SOCKET_cant_recv_more |
; |
; IN: eax = socket ptr |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_cant_recv_more: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_cant_recv_more: %x\n", eax |
or [eax + SOCKET.state], SS_CANTRCVMORE |
call SOCKET_notify |
ret |
;----------------------------------------------------------------- |
; |
; SOCKET_cant_send_more |
; |
; IN: eax = socket ptr |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
SOCKET_cant_send_more: |
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_cant_send_more: %x\n", eax |
or [eax + SOCKET.state], SS_CANTSENDMORE |
mov [eax + SOCKET.snd_proc], .notconn |
call SOCKET_notify |
ret |
.notconn: |
mov dword[esp+20], ENOTCONN |
mov dword[esp+32], -1 |
ret |
/kernel/branches/kolibri-process/network/stack.inc |
---|
0,0 → 1,791 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; STACK.INC ;; |
;; ;; |
;; TCP/IP stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; Some parts of code are based on the work of: ;; |
;; Mike Hibbett (menuetos network stack) ;; |
;; Eugen Brasoveanu (solar os network stack and drivers) ;; |
;; mike.dld (kolibrios socket code) ;; |
;; ;; |
;; TCP part is based on 4.4BSD ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3523 $ |
uglobal |
net_10ms dd ? |
net_tmr_count dw ? |
endg |
DEBUG_NETWORK_ERROR = 1 |
DEBUG_NETWORK_VERBOSE = 0 |
NET_DEVICES_MAX = 16 |
ARP_BLOCK = 1 ; true or false |
EPHEMERAL_PORT_MIN = 49152 |
EPHEMERAL_PORT_MAX = 61000 |
MIN_EPHEMERAL_PORT_N = 0x00C0 ; same in Network byte order (FIXME) |
MAX_EPHEMERAL_PORT_N = 0x48EE ; same in Network byte order (FIXME) |
; Ethernet protocol numbers |
ETHER_PROTO_ARP = 0x0608 |
ETHER_PROTO_IPv4 = 0x0008 |
ETHER_PROTO_IPv6 = 0xDD86 |
ETHER_PROTO_PPP_DISCOVERY = 0x6388 |
ETHER_PROTO_PPP_SESSION = 0x6488 |
; Internet protocol numbers |
IP_PROTO_IP = 0 |
IP_PROTO_ICMP = 1 |
IP_PROTO_TCP = 6 |
IP_PROTO_UDP = 17 |
; PPP protocol numbers |
PPP_PROTO_IPv4 = 0x2100 |
PPP_PROTO_IPV6 = 0x5780 |
PPP_PROTO_ETHERNET = 666 ; FIXME |
;Protocol family |
AF_UNSPEC = 0 |
AF_LOCAL = 1 |
AF_INET4 = 2 |
AF_INET6 = 10 |
AF_PPP = 777 ; FIXME |
; Socket types |
SOCK_STREAM = 1 |
SOCK_DGRAM = 2 |
SOCK_RAW = 3 |
; Socket options |
SO_ACCEPTCON = 1 shl 0 |
SO_BROADCAST = 1 shl 1 |
SO_DEBUG = 1 shl 2 |
SO_DONTROUTE = 1 shl 3 |
SO_KEEPALIVE = 1 shl 4 |
SO_OOBINLINE = 1 shl 5 |
SO_REUSEADDR = 1 shl 6 |
SO_REUSEPORT = 1 shl 7 |
SO_USELOOPBACK = 1 shl 8 |
SO_BINDTODEVICE = 1 shl 9 |
SO_NONBLOCK = 1 shl 31 |
; Socket flags for user calls |
MSG_PEEK = 0x02 |
MSG_DONTWAIT = 0x40 |
; Socket level |
SOL_SOCKET = 0 |
; Socket States |
SS_NOFDREF = 0x0001 ; no file table ref any more |
SS_ISCONNECTED = 0x0002 ; socket connected to a peer |
SS_ISCONNECTING = 0x0004 ; in process of connecting to peer |
SS_ISDISCONNECTING = 0x0008 ; in process of disconnecting |
SS_CANTSENDMORE = 0x0010 ; can't send more data to peer |
SS_CANTRCVMORE = 0x0020 ; can't receive more data from peer |
SS_RCVATMARK = 0x0040 ; at mark on input |
SS_ISABORTING = 0x0080 ; aborting fd references - close() |
SS_RESTARTSYS = 0x0100 ; restart blocked system calls |
SS_ISDISCONNECTED = 0x0800 ; socket disconnected from peer |
SS_ASYNC = 0x1000 ; async i/o notify |
SS_ISCONFIRMING = 0x2000 ; deciding to accept connection req |
SS_MORETOCOME = 0x4000 |
SS_BLOCKED = 0x8000 |
SOCKET_MAXDATA = 4096*8 ; must be 4096*(power of 2) where 'power of 2' is at least 8 |
MAX_backlog = 20 ; maximum backlog for stream sockets |
; Error Codes |
ENOBUFS = 1 |
EINPROGRESS = 2 |
EOPNOTSUPP = 4 |
EWOULDBLOCK = 6 |
ENOTCONN = 9 |
EALREADY = 10 |
EINVAL = 11 |
EMSGSIZE = 12 |
ENOMEM = 18 |
EADDRINUSE = 20 |
ECONNREFUSED = 61 |
ECONNRESET = 52 |
EISCONN = 56 |
ETIMEDOUT = 60 |
ECONNABORTED = 53 |
; Api protocol numbers |
API_ETH = 0 |
API_IPv4 = 1 |
API_ICMP = 2 |
API_UDP = 3 |
API_TCP = 4 |
API_ARP = 5 |
API_PPPOE = 6 |
API_IPv6 = 7 |
; Network device types |
NET_DEVICE_LOOPBACK = 0 |
NET_DEVICE_ETH = 1 |
NET_DEVICE_SLIP = 2 |
; Network link types (link protocols) |
NET_LINK_LOOPBACK = 0 ;;; Really a link type? |
NET_LINK_MAC = 1 ; Media access control (ethernet, isdn, ...) |
NET_LINK_PPP = 2 ; Point to Point Protocol (PPPoE, ...) |
NET_LINK_IEEE802.11 = 3 ; IEEE 802.11 (WiFi) |
; Hardware acceleration bits |
NET_HWACC_TCP_IPv4_IN = 1 shl 0 |
NET_HWACC_TCP_IPv4_OUT = 1 shl 1 |
struct NET_DEVICE |
device_type dd ? ; Type field |
mtu dd ? ; Maximal Transmission Unit |
name dd ? ; Ptr to 0 terminated string |
unload dd ? ; Ptrs to driver functions |
reset dd ? ; |
transmit dd ? ; |
bytes_tx dq ? ; Statistics, updated by the driver |
bytes_rx dq ? ; |
packets_tx dd ? ; |
packets_rx dd ? ; |
link_state dd ? ; link state (0 = no link) |
hwacc dd ? ; bitmask stating enabled HW accelerations (offload engines) |
ends |
; Exactly as it says.. |
macro pseudo_random reg { |
add reg, [esp] |
rol reg, 5 |
xor reg, [timer_ticks] |
; add reg, [CPU_FREQ] |
imul reg, 214013 |
xor reg, 0xdeadbeef |
rol reg, 9 |
} |
; Network to Hardware byte order (dword) |
macro ntohd reg { |
rol word reg, 8 |
rol dword reg, 16 |
rol word reg , 8 |
} |
; Network to Hardware byte order (word) |
macro ntohw reg { |
rol word reg, 8 |
} |
include "queue.inc" |
include "loopback.inc" |
include "ethernet.inc" |
include "PPPoE.inc" |
include "ARP.inc" |
include "IPv4.inc" |
include "IPv6.inc" |
include "icmp.inc" |
include "udp.inc" |
include "tcp.inc" |
include "socket.inc" |
uglobal |
align 4 |
NET_RUNNING dd ? |
NET_DRV_LIST rd NET_DEVICES_MAX |
endg |
;----------------------------------------------------------------- |
; |
; stack_init |
; |
; This function calls all network init procedures |
; |
; IN: / |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
stack_init: |
; Init the network drivers list |
xor eax, eax |
mov edi, NET_RUNNING |
mov ecx, (NET_DEVICES_MAX + 2) |
rep stosd |
ETH_init |
PPPoE_init |
IPv4_init |
; IPv6_init |
ICMP_init |
ARP_init |
UDP_init |
TCP_init |
SOCKET_init |
LOOP_init |
mov [net_tmr_count], 0 |
ret |
; Wakeup every tick. |
proc stack_handler_has_work? |
mov eax, [timer_ticks] |
cmp eax, [net_10ms] |
ret |
endp |
;----------------------------------------------------------------- |
; |
; stack_handler |
; |
; This function is called in kernel loop |
; |
; IN: / |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
stack_handler: |
; Test for 10ms tick |
mov eax, [timer_ticks] |
cmp eax, [net_10ms] |
je .exit |
mov [net_10ms], eax |
cmp [NET_RUNNING], 0 |
je .exit |
test [net_10ms], 0x0f ; 160ms |
jnz .exit |
TCP_timer_160ms |
test [net_10ms], 0x3f ; 640ms |
jnz .exit |
TCP_timer_640ms |
ARP_decrease_entry_ttls |
IPv4_decrease_fragment_ttls |
.exit: |
ret |
align 4 |
NET_packet_free: |
and dword[esp+4], not 0xfff |
jmp kernel_free |
align 4 |
NET_link_changed: |
DEBUGF DEBUG_NETWORK_VERBOSE, "NET_link_changed device=0x%x status=0x%x\n", ebx, [ebx + NET_DEVICE.link_state] |
align 4 |
NET_send_event: |
DEBUGF DEBUG_NETWORK_VERBOSE, "NET_send_event\n" |
; Send event to all applications |
push edi ecx |
mov edi, SLOT_BASE |
mov ecx, [TASK_COUNT] |
.loop: |
add edi, 256 |
or [edi + APPDATA.event_mask], EVENT_NETWORK2 |
loop .loop |
pop ecx edi |
ret |
;----------------------------------------------------------------- |
; |
; NET_add_device: |
; |
; This function is called by the network drivers, |
; to register each running NIC to the kernel |
; |
; IN: Pointer to device structure in ebx |
; OUT: Device num in eax, -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
NET_add_device: |
DEBUGF DEBUG_NETWORK_VERBOSE, "NET_Add_Device: %x\n", ebx ;;; TODO: use mutex to lock net device list |
cmp [NET_RUNNING], NET_DEVICES_MAX |
jae .error |
;---------------------------------- |
; Check if device is already listed |
mov eax, ebx |
mov ecx, NET_DEVICES_MAX ; We need to check whole list because a device may be removed without re-organizing list |
mov edi, NET_DRV_LIST |
repne scasd ; See if device is already in the list |
jz .error |
;---------------------------- |
; Find empty slot in the list |
xor eax, eax |
mov ecx, NET_DEVICES_MAX |
mov edi, NET_DRV_LIST |
repne scasd |
jnz .error |
sub edi, 4 |
;----------------------------- |
; Add device to the found slot |
mov [edi], ebx ; add device to list |
mov eax, edi ; Calculate device number in eax |
sub eax, NET_DRV_LIST |
shr eax, 2 |
inc [NET_RUNNING] ; Indicate that one more network device is up and running |
call NET_send_event |
DEBUGF DEBUG_NETWORK_VERBOSE, "Device number: %u\n", eax |
ret |
.error: |
or eax, -1 |
DEBUGF DEBUG_NETWORK_ERROR, "Adding network device failed\n" |
ret |
;----------------------------------------------------------------- |
; |
; NET_Remove_Device: |
; |
; This function is called by network drivers, |
; to unregister network devices from the kernel |
; |
; IN: Pointer to device structure in ebx |
; OUT: eax: -1 on error |
; |
;----------------------------------------------------------------- |
align 4 |
NET_remove_device: |
cmp [NET_RUNNING], 0 |
je .error |
;---------------------------- |
; Find the driver in the list |
mov eax, ebx |
mov ecx, NET_DEVICES_MAX |
mov edi, NET_DRV_LIST |
repne scasd |
jnz .error |
;------------------------ |
; Remove it from the list |
xor eax, eax |
mov dword [edi-4], eax |
dec [NET_RUNNING] |
call NET_send_event |
xor eax, eax |
ret |
.error: |
or eax, -1 |
ret |
;----------------------------------------------------------------- |
; |
; NET_ptr_to_num |
; |
; IN: ebx = ptr to device struct |
; OUT: edi = -1 on error, device number otherwise |
; |
;----------------------------------------------------------------- |
align 4 |
NET_ptr_to_num: |
call NET_ptr_to_num4 |
ror edi, 2 ; If -1, stay -1 |
; valid device numbers have last two bits 0, so do just shr |
ret |
align 4 |
NET_ptr_to_num4: ; Todo, place number in device structure so we only need to verify? |
push ecx |
mov ecx, NET_DEVICES_MAX |
mov edi, NET_DRV_LIST |
.loop: |
cmp ebx, [edi] |
je .found |
add edi, 4 |
dec ecx |
jnz .loop |
or edi, -1 |
pop ecx |
ret |
.found: |
sub edi, NET_DRV_LIST |
pop ecx |
ret |
;----------------------------------------------------------------- |
; |
; checksum_1 |
; |
; This is the first of two functions needed to calculate a checksum. |
; |
; IN: edx = start offset for semi-checksum |
; esi = pointer to data |
; ecx = data size |
; OUT: edx = semi-checksum |
; |
; |
; Code was optimized by diamond |
; |
;----------------------------------------------------------------- |
align 4 |
checksum_1: |
shr ecx, 1 |
pushf |
jz .no_2 |
shr ecx, 1 |
pushf |
jz .no_4 |
shr ecx, 1 |
pushf |
jz .no_8 |
.loop: |
add dl, [esi+1] |
adc dh, [esi+0] |
adc dl, [esi+3] |
adc dh, [esi+2] |
adc dl, [esi+5] |
adc dh, [esi+4] |
adc dl, [esi+7] |
adc dh, [esi+6] |
adc edx, 0 |
add esi, 8 |
dec ecx |
jnz .loop |
adc edx, 0 |
.no_8: |
popf |
jnc .no_4 |
add dl, [esi+1] |
adc dh, [esi+0] |
adc dl, [esi+3] |
adc dh, [esi+2] |
adc edx, 0 |
add esi, 4 |
.no_4: |
popf |
jnc .no_2 |
add dl, [esi+1] |
adc dh, [esi+0] |
adc edx, 0 |
inc esi |
inc esi |
.no_2: |
popf |
jnc .end |
add dh, [esi+0] |
adc edx, 0 |
.end: |
ret |
;----------------------------------------------------------------- |
; |
; checksum_2 |
; |
; This function calculates the final ip/tcp/udp checksum for you |
; |
; IN: edx = semi-checksum |
; OUT: dx = checksum (in INET byte order) |
; |
;----------------------------------------------------------------- |
align 4 |
checksum_2: |
mov ecx, edx |
shr ecx, 16 |
and edx, 0xffff |
add edx, ecx |
mov ecx, edx |
shr ecx, 16 |
add dx, cx |
test dx, dx ; it seems that ZF is not set when CF is set :( |
not dx |
jnz .not_zero |
dec dx |
.not_zero: |
xchg dl, dh |
DEBUGF DEBUG_NETWORK_VERBOSE, "Checksum: %x\n", dx |
ret |
;---------------------------------------------------------------- |
; |
; System function to work with network devices (74) |
; |
;---------------------------------------------------------------- |
align 4 |
sys_network: |
cmp bl, 255 |
jne @f |
mov eax, [NET_RUNNING] |
mov [esp+32], eax |
ret |
@@: |
cmp bh, NET_DEVICES_MAX ; Check if device number exists |
jae .doesnt_exist |
mov esi, ebx |
and esi, 0x0000ff00 |
shr esi, 6 |
cmp dword [esi + NET_DRV_LIST], 0 ; check if driver is running |
je .doesnt_exist |
mov eax, [esi + NET_DRV_LIST] |
and ebx, 0x000000ff |
cmp ebx, .number |
ja .doesnt_exist |
jmp dword [.table + 4*ebx] |
.table: |
dd .get_type ; 0 |
dd .get_dev_name ; 1 |
dd .reset ; 2 |
dd .stop ; 3 |
dd .get_ptr ; 4 |
dd .get_drv_name ; 5 |
dd .packets_tx ; 6 |
dd .packets_rx ; 7 |
dd .bytes_tx ; 8 |
dd .bytes_rx ; 9 |
dd .state ; 10 |
.number = ($ - .table) / 4 - 1 |
.get_type: |
mov eax, [eax + NET_DEVICE.device_type] |
mov [esp+32], eax |
ret |
.get_dev_name: |
mov esi, [eax + NET_DEVICE.name] |
mov edi, ecx |
mov ecx, 64/4 ; max length |
rep movsd |
xor eax, eax |
mov [esp+32], eax |
ret |
.reset: |
call [eax + NET_DEVICE.reset] |
mov [esp+32], eax |
ret |
.stop: |
call [eax + NET_DEVICE.unload] |
mov [esp+32], eax |
ret |
.get_ptr: |
mov [esp+32], eax |
ret |
.get_drv_name: |
xor eax, eax |
mov [esp+32], eax |
ret |
.packets_tx: |
mov eax, [eax + NET_DEVICE.packets_tx] |
mov [esp+32], eax |
ret |
.packets_rx: |
mov eax, [eax + NET_DEVICE.packets_rx] |
mov [esp+32], eax |
ret |
.bytes_tx: |
mov ebx, dword [eax + NET_DEVICE.bytes_tx + 4] |
mov [esp+20], ebx |
mov eax, dword [eax + NET_DEVICE.bytes_tx] |
mov [esp+32], eax |
ret |
.bytes_rx: |
mov ebx, dword [eax + NET_DEVICE.bytes_rx + 4] |
mov [esp+20], ebx |
mov eax, dword [eax + NET_DEVICE.bytes_rx] |
mov [esp+32], eax |
ret |
.state: |
mov eax, [eax + NET_DEVICE.link_state] |
mov [esp+32], eax |
ret |
.doesnt_exist: |
mov dword[esp+32], -1 |
ret |
;---------------------------------------------------------------- |
; |
; System function to work with protocols (76) |
; |
;---------------------------------------------------------------- |
align 4 |
sys_protocols: |
cmp bh, NET_DEVICES_MAX ; Check if device number exists |
jae .doesnt_exist |
mov esi, ebx |
and esi, 0x0000ff00 |
shr esi, 6 ; now we have the device num * 4 in esi |
cmp [esi + NET_DRV_LIST], 0 ; check if driver is running |
je .doesnt_exist |
push .return ; return address (we will be using jumps instead of calls) |
mov eax, ebx ; set ax to protocol number |
shr eax, 16 ; |
cmp ax, API_ETH |
je ETH_api |
cmp ax, API_IPv4 |
je IPv4_api |
cmp ax, API_ICMP |
je ICMP_api |
cmp ax, API_UDP |
je UDP_api |
cmp ax, API_TCP |
je TCP_api |
cmp ax, API_ARP |
je ARP_api |
cmp ax, API_PPPOE |
je PPPoE_api |
cmp ax, API_IPv6 |
je IPv6_api |
add esp, 4 ; if we reached here, no function was called, so we need to balance stack |
.doesnt_exist: |
mov eax, -1 |
.return: |
mov [esp+28+4], eax ; return eax value to the program |
ret |
/kernel/branches/kolibri-process/network/tcp.inc |
---|
0,0 → 1,286 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; Part of the TCP/IP network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; Based on the code of 4.4BSD ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3406 $ |
; Socket states |
TCPS_CLOSED = 0 |
TCPS_LISTEN = 1 |
TCPS_SYN_SENT = 2 |
TCPS_SYN_RECEIVED = 3 |
TCPS_ESTABLISHED = 4 |
TCPS_CLOSE_WAIT = 5 |
TCPS_FIN_WAIT_1 = 6 |
TCPS_CLOSING = 7 |
TCPS_LAST_ACK = 8 |
TCPS_FIN_WAIT_2 = 9 |
TCPS_TIMED_WAIT = 10 |
; Socket Flags |
TF_ACKNOW = 1 shl 0 ; ack peer immediately |
TF_DELACK = 1 shl 1 ; ack, but try to delay it |
TF_NODELAY = 1 shl 2 ; don't delay packets to coalesce |
TF_NOOPT = 1 shl 3 ; don't use tcp options |
TF_SENTFIN = 1 shl 4 ; have sent FIN |
TF_REQ_SCALE = 1 shl 5 ; have/will request window scaling |
TF_RCVD_SCALE = 1 shl 6 ; other side has requested scaling |
TF_REQ_TSTMP = 1 shl 7 ; have/will request timestamps |
TF_RCVD_TSTMP = 1 shl 8 ; a timestamp was received in SYN |
TF_SACK_PERMIT = 1 shl 9 ; other side said I could SACK |
; Segment flags |
TH_FIN = 1 shl 0 |
TH_SYN = 1 shl 1 |
TH_RST = 1 shl 2 |
TH_PUSH = 1 shl 3 |
TH_ACK = 1 shl 4 |
TH_URG = 1 shl 5 |
; Segment header options |
TCP_OPT_EOL = 0 ; End of option list. |
TCP_OPT_NOP = 1 ; No-Operation. |
TCP_OPT_MAXSEG = 2 ; Maximum Segment Size. |
TCP_OPT_WINDOW = 3 ; window scale |
TCP_OPT_SACK_PERMIT = 4 ; Selective Acknowledgement |
TCP_OPT_SACK = 5 |
TCP_OPT_TIMESTAMP = 8 |
; Fundamental timer values |
TCP_time_MSL = 47 ; max segment lifetime (30s) |
TCP_time_re_min = 2 ; min retransmission (1,28s) |
TCP_time_re_max = 100 ; max retransmission (64s) |
TCP_time_pers_min = 8 ; min persist (5,12s) |
TCP_time_pers_max = 94 ; max persist (60,16s) |
TCP_time_keep_init = 118 ; connection establishment (75,52s) |
TCP_time_keep_idle = 4608 ; idle time before 1st probe (2h) |
TCP_time_keep_interval = 118 ; between probes when no response (75,52s) |
TCP_time_rtt_default = 5 ; default Round Trip Time (3,2s) |
TCP_time_srtt_default = 0 ; |
TCP_time_max_idle = 8*TCP_time_keep_interval ; FIXME |
TCP_time_connect = 300 ; in 1/100s (default=3s) |
; timer constants |
TCP_max_rxtshift = 12 ; max retransmissions waiting for ACK |
TCP_max_keepcnt = 8 ; max keepalive probes |
; |
TCP_max_winshift = 14 |
TCP_max_win = 65535 |
TCP_re_xmit_thresh = 3 |
TCP_mss_default = 1480 ; default max segment size |
; smoothed round trip time and estimated variance are stored as fixed point numbers, |
; shifted by the value below. |
; With these scales, srtt has 3 bits to the right of the binary point, and thus an "alpha" |
; of .875. rttvar has 2 bits to the right and thus "alpha" of 0.75 |
TCP_RTT_SHIFT = 3 |
TCP_RTTVAR_SHIFT = 2 |
; bits used by tcp_input and tcp_output |
TCP_BIT_NEEDOUTPUT = 1 shl 0 |
TCP_BIT_TIMESTAMP = 1 shl 1 |
TCP_BIT_DROPSOCKET = 1 shl 2 |
TCP_BIT_SENDALOT = 1 shl 0 |
TCP_PAWS_IDLE = 24*24*60*60*100 ; 24 days, in 1/100 seconds |
TCP_QUEUE_SIZE = 50 |
struct TCP_header |
SourcePort dw ? |
DestinationPort dw ? |
SequenceNumber dd ? |
AckNumber dd ? |
DataOffset db ? ; DataOffset[0-3 bits] and Reserved[4-7] |
Flags db ? ; Reserved[0-1 bits]|URG|ACK|PSH|RST|SYN|FIN |
Window dw ? |
Checksum dw ? |
UrgentPointer dw ? |
ends |
struct TCP_queue_entry |
ip_ptr dd ? |
segment_ptr dd ? |
segment_size dd ? |
device_ptr dd ? |
buffer_ptr dd ? |
timestamp dd ? |
ends |
uglobal |
align 4 |
TCP_segments_tx rd NET_DEVICES_MAX |
TCP_segments_rx rd NET_DEVICES_MAX |
TCP_segments_missed rd NET_DEVICES_MAX |
TCP_segments_dumped rd NET_DEVICES_MAX |
; TCP_bytes_rx rq NET_DEVICES_MAX |
; TCP_bytes_tx rq NET_DEVICES_MAX |
TCP_sequence_num dd ? |
TCP_queue rd (TCP_QUEUE_SIZE*sizeof.TCP_queue_entry + sizeof.queue)/4 |
TCP_input_event dd ? |
endg |
uglobal |
align 4 |
TCPS_accepts dd ? ; #SYNs received in LISTEN state |
TCPS_closed dd ? ; #connections closed (includes drops) |
TCPS_connattempt dd ? ; #connections initiated (calls to connect) |
TCPS_conndrops dd ? ; #embryonic connections dropped (before SYN received) |
TCPS_connects dd ? ; #connections established actively or passively |
TCPS_delack dd ? ; #delayed ACKs sent |
TCPS_drops dd ? ; #connections dropped (after SYN received) |
TCPS_keepdrops dd ? ; #connections dropped in keepalive (established or awaiting SYN) |
TCPS_keepprobe dd ? ; #keepalive probes sent |
TCPS_keeptimeo dd ? ; #times keepalive timer or connections-establishment timer expire |
TCPS_pawsdrop dd ? ; #segments dropped due to PAWS |
TCPS_pcbcachemiss dd ? ; #times PCB cache comparison fails |
TCPS_persisttimeo dd ? ; #times persist timer expires |
TCPS_predack dd ? ; #times header prediction correct for ACKs |
TCPS_preddat dd ? ; #times header prediction correct for data packets |
TCPS_rcvackbyte dd ? ; #bytes ACKed by received ACKs |
TCPS_rcvackpack dd ? ; #received ACK packets |
TCPS_rcvacktoomuch dd ? ; #received ACKs for unsent data |
TCPS_rcvafterclose dd ? ; #packets received after connection closed |
TCPS_rcvbadoff dd ? ; #packets received with invalid header length |
TCPS_rcvbadsum dd ? ; #packets received with checksum errors |
TCPS_rcvbyte dd ? ; #bytes received in sequence |
TCPS_rcvbyteafterwin dd ? ; #bytes received beyond advertised window |
TCPS_rcvdupack dd ? ; #duplicate ACKs received |
TCPS_rcvdupbyte dd ? ; #bytes receivedin completely duplicate packets |
TCPS_rcvduppack dd ? ; #packets received with completely duplicate bytes |
TCPS_rcvoobyte dd ? ; #out-of-order bytes received |
TCPS_rcvoopack dd ? ; #out-of-order packets received |
TCPS_rcvpack dd ? ; #packets received in sequence |
TCPS_rcvpackafterwin dd ? ; #packets with some data beyond advertised window |
TCPS_rcvpartdupbyte dd ? ; #duplicate bytes in part-duplicate packets |
TCPS_rcvpartduppack dd ? ; #packets with some duplicate data |
TCPS_rcvshort dd ? ; #packets received too short |
TCPS_rcvtotal dd ? ; #total packets received |
TCPS_rcvwinprobe dd ? ; #window probe packets received |
TCPS_rcvwinupd dd ? ; #received window update packets |
TCPS_rexmttimeo dd ? ; #retransmission timeouts |
TCPS_rttupdated dd ? ; #times RTT estimators updated |
TCPS_segstimed dd ? ; #segments for which TCP tried to measure RTT |
TCPS_sndacks dd ? ; #ACK-only packets sent (data length = 0) |
TCPS_sndbyte dd ? ; #data bytes sent |
TCPS_sndctrl dd ? ; #control (SYN, FIN, RST) packets sent (data length = 0) |
TCPS_sndpack dd ? ; #data packets sent (data length > 0) |
TCPS_sndprobe dd ? ; #window probes sent (1 byte of data forced by persist timer) |
TCPS_sndrexmitbyte dd ? ; #data bytes retransmitted |
TCPS_sndrexmitpack dd ? ; #data packets retransmitted |
TCPS_sndtotal dd ? ; total #packets sent |
TCPS_sndurg dd ? ; #packets sent with URG-only (data length=0) |
TCPS_sndwinup dd ? ; #window update-only packets sent (data length=0) |
TCPS_timeoutdrop dd ? ; #connections dropped in retransmission timeout |
endg |
;----------------------------------------------------------------- |
; |
; TCP_init |
; |
; This function resets all TCP variables |
; |
;----------------------------------------------------------------- |
macro TCP_init { |
xor eax, eax |
mov edi, TCP_segments_tx |
mov ecx, (6*NET_DEVICES_MAX) |
rep stosd |
pseudo_random eax |
mov [TCP_sequence_num], eax |
init_queue TCP_queue |
movi ebx, 1 |
mov ecx, TCP_process_input |
call new_sys_threads |
test eax, eax |
jns @f |
DEBUGF DEBUG_NETWORK_ERROR,'K : cannot create kernel thread for TCP, error %d\n', eax |
@@: |
} |
include 'tcp_timer.inc' |
include 'tcp_subr.inc' |
include 'tcp_usreq.inc' |
include 'tcp_input.inc' |
include 'tcp_output.inc' |
;--------------------------------------------------------------------------- |
; |
; TCP_API |
; |
; This function is called by system function 76 |
; |
; IN: subfunction number in bl |
; device number in bh |
; ecx, edx, .. depends on subfunction |
; |
; OUT: |
; |
;--------------------------------------------------------------------------- |
align 4 |
TCP_api: |
movzx eax, bh |
shl eax, 2 |
test bl, bl |
jz .packets_tx ; 0 |
dec bl |
jz .packets_rx ; 1 |
dec bl |
jz .packets_missed ; 2 |
dec bl |
jz .packets_dumped ; 3 |
.error: |
mov eax, -1 |
ret |
.packets_tx: |
mov eax, [TCP_segments_tx + eax] |
ret |
.packets_rx: |
mov eax, [TCP_segments_rx + eax] |
ret |
.packets_missed: |
mov eax, [TCP_segments_missed + eax] |
ret |
.packets_dumped: |
mov eax, [TCP_segments_dumped + eax] |
ret |
/kernel/branches/kolibri-process/network/tcp_input.inc |
---|
0,0 → 1,1719 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; Part of the TCP/IP network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; Based on the code of 4.4BSD ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3407 $ |
;----------------------------------------------------------------- |
; |
; TCP_input: |
; |
; Add a segment to the incoming TCP queue |
; |
; IN: [esp] = ptr to buffer |
; [esp+4] = buffer size (dont care) |
; ebx = ptr to device struct |
; ecx = segment size |
; esi = ptr to TCP segment |
; edi = ptr to ipv4 source address, followed by ipv4 dest address |
; |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
TCP_input: |
; record the current time |
mov eax, [timer_ticks] ; in 1/100 seconds |
mov [esp + 4], eax |
push ebx ecx esi edi ; mind the order |
mov esi, esp |
pushf |
cli |
add_to_queue TCP_queue, TCP_QUEUE_SIZE, sizeof.TCP_queue_entry, .fail |
popf |
add esp, sizeof.TCP_queue_entry |
call NET_ptr_to_num4 |
inc [TCP_segments_rx + edi] |
xor edx, edx |
mov eax, [TCP_input_event] |
mov ebx, [eax + EVENT.id] |
xor esi, esi |
call raise_event |
ret |
.fail: |
popf |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP incoming queue is full, discarding packet!\n" |
call NET_ptr_to_num4 |
inc [TCP_segments_missed + edi] |
add esp, sizeof.TCP_queue_entry - 8 |
call NET_packet_free |
add esp, 4 |
ret |
align 4 |
proc TCP_process_input |
locals |
dataoffset dd ? |
timestamp dd ? |
temp_bits db ? |
endl |
xor esi, esi |
mov ecx, MANUAL_DESTROY |
call create_event |
mov [TCP_input_event], eax |
.wait: |
mov eax, [TCP_input_event] |
mov ebx, [eax + EVENT.id] |
call wait_event |
.loop: |
get_from_queue TCP_queue, TCP_QUEUE_SIZE, sizeof.TCP_queue_entry, .wait |
push [esi + TCP_queue_entry.timestamp] |
pop [timestamp] |
push [esi + TCP_queue_entry.buffer_ptr] |
mov ebx, [esi + TCP_queue_entry.device_ptr] |
mov ecx, [esi + TCP_queue_entry.segment_size] |
mov edi, [esi + TCP_queue_entry.ip_ptr] ; ptr to ipv4 source address, followed by ipv4 destination address |
mov esi, [esi + TCP_queue_entry.segment_ptr] ; change esi last |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: size=%u time=%d\n", ecx, [timer_ticks] |
mov edx, esi |
cmp ebx, LOOPBACK_DEVICE |
je .checksum_ok |
; re-calculate the checksum (if not already done by hw) |
test [ebx + NET_DEVICE.hwacc], NET_HWACC_TCP_IPv4_IN |
jnz .checksum_ok |
push ecx esi |
pushw [esi + TCP_header.Checksum] |
mov [esi + TCP_header.Checksum], 0 |
TCP_checksum (edi), (edi+4) |
pop cx ; previous checksum |
cmp cx, dx |
pop edx ecx |
jne .drop_no_socket |
.checksum_ok: |
; Verify the data offset |
movzx eax, [edx + TCP_header.DataOffset] |
and al, 0xf0 ; Calculate TCP segment header size (throwing away unused reserved bits in TCP header) |
shr al, 2 |
cmp al, sizeof.TCP_header ; Now see if it's at least the size of a standard TCP header |
jb .drop_no_socket ; If not, drop the packet |
mov [dataoffset], eax |
sub ecx, eax ; substract TCP header size from total segment size |
jb .drop_no_socket ; If total segment size is less then the advertised header size, drop packet |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: %u bytes of data\n", ecx |
;------------------------------------------- |
; Convert Big-endian values to little endian |
ntohd [edx + TCP_header.SequenceNumber] |
ntohd [edx + TCP_header.AckNumber] |
ntohw [edx + TCP_header.Window] |
ntohw [edx + TCP_header.UrgentPointer] |
;------------------------ |
; Find the socket pointer |
; IP Packet TCP Destination Port = local Port |
; (IP Packet SenderAddress = Remote IP) OR (Remote IP = 0) |
; (IP Packet TCP Source Port = remote Port) OR (remote Port = 0) |
.findpcb: |
pusha |
mov ecx, socket_mutex |
call mutex_lock |
popa |
mov ebx, net_sockets |
mov si, [edx + TCP_header.DestinationPort] |
.socket_loop: |
mov ebx, [ebx + SOCKET.NextPtr] |
or ebx, ebx |
jz .no_socket ;respond_seg_reset |
cmp [ebx + SOCKET.Domain], AF_INET4 |
jne .socket_loop |
cmp [ebx + SOCKET.Protocol], IP_PROTO_TCP |
jne .socket_loop |
cmp [ebx + TCP_SOCKET.LocalPort], si |
jne .socket_loop |
mov eax, [ebx + IP_SOCKET.RemoteIP] |
cmp eax, [edi] ; Ipv4 source address |
je @f |
test eax, eax |
jnz .socket_loop |
@@: |
mov ax, [ebx + TCP_SOCKET.RemotePort] |
cmp [edx + TCP_header.SourcePort], ax |
je .found_socket |
test ax, ax |
jnz .socket_loop |
.found_socket: ; ebx now contains the socketpointer |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: socket ptr=%x state=%u flags=%x\n", ebx, [ebx + TCP_SOCKET.t_state], [edx + TCP_header.Flags]:2 |
;---------------------------- |
; Check if socket isnt closed |
cmp [ebx + TCP_SOCKET.t_state], TCPS_CLOSED |
je .drop_no_socket |
;---------------- |
; Lock the socket |
pusha |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_lock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: socket locked\n" |
;--------------------------- |
; disable all temporary bits |
mov [temp_bits], 0 |
;--------------------------------------- |
; unscale the window into a 32 bit value |
movzx eax, [edx + TCP_header.Window] |
push ecx |
mov cl, [ebx + TCP_SOCKET.SND_SCALE] |
shl eax, cl |
mov dword [edx + TCP_header.Window], eax ; word after window is checksum, we dont need checksum anymore |
pop ecx |
;--------------------------------------- |
; Are we accepting incoming connections? |
test [ebx + SOCKET.options], SO_ACCEPTCON |
jz .no_accept |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Accepting new connection\n" |
pusha |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
popa |
push ecx edx esi edi ;;; |
call SOCKET_fork |
pop edi esi edx ecx |
test eax, eax |
jz .drop_no_socket |
mov ebx, eax |
mov [temp_bits], TCP_BIT_DROPSOCKET |
push dword [edi + 4] ; Ipv4 destination addres |
pop [ebx + IP_SOCKET.LocalIP] |
push [edx + TCP_header.DestinationPort] |
pop [ebx + TCP_SOCKET.LocalPort] |
mov [ebx + TCP_SOCKET.t_state], TCPS_LISTEN |
.no_accept: |
;------------------------------------- |
; Reset idle timer and keepalive timer |
mov [ebx + TCP_SOCKET.t_idle], 0 |
mov [ebx + TCP_SOCKET.timer_keepalive], TCP_time_keep_idle |
or [ebx + TCP_SOCKET.timer_flags], timer_flag_keepalive |
;-------------------- |
; Process TCP options |
;;; FIXME: for LISTEN, options should be called after we determined route, we need it for MSS |
;;; cmp [ebx + TCP_SOCKET.t_state], TCPS_LISTEN ; no options when in listen state |
;;; jz .not_uni_xfer ; also no header prediction |
push ecx |
mov ecx, [dataoffset] |
cmp ecx, sizeof.TCP_header ; Does header contain any options? |
je .no_options |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Segment has options\n" |
add ecx, edx |
lea esi, [edx + sizeof.TCP_header] |
.opt_loop: |
cmp esi, ecx ; are we scanning outside of header? |
jae .no_options |
lodsb |
cmp al, TCP_OPT_EOL ; end of option list? |
je .no_options |
cmp al, TCP_OPT_NOP |
je .opt_loop |
cmp al, TCP_OPT_MAXSEG |
je .opt_maxseg |
cmp al, TCP_OPT_WINDOW |
je .opt_window |
cmp al, TCP_OPT_SACK_PERMIT |
je .opt_sack_permit |
; cmp al, TCP_OPT_SACK |
; je .opt_sack |
cmp al, TCP_OPT_TIMESTAMP |
je .opt_timestamp |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: unknown option:%u\n", al |
jmp .no_options ; If we reach here, some unknown options were received, skip them all! |
.opt_maxseg: |
lodsb |
cmp al, 4 |
jne .no_options ; error occured, ignore all options! |
test [edx + TCP_header.Flags], TH_SYN |
jz @f |
xor eax, eax |
lodsw |
rol ax, 8 |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Maxseg=%u\n", eax |
call TCP_mss |
@@: |
jmp .opt_loop |
.opt_window: |
lodsb |
cmp al, 3 |
jne .no_options |
test [edx + TCP_header.Flags], TH_SYN |
jz @f |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Got window scale option\n" |
or [ebx + TCP_SOCKET.t_flags], TF_RCVD_SCALE |
lodsb |
mov [ebx + TCP_SOCKET.SND_SCALE], al |
;;;;; TODO |
@@: |
jmp .opt_loop |
.opt_sack_permit: |
lodsb |
cmp al, 2 |
jne .no_options |
test [edx + TCP_header.Flags], TH_SYN |
jz @f |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Selective Acknowledgement permitted\n" |
or [ebx + TCP_SOCKET.t_flags], TF_SACK_PERMIT |
@@: |
jmp .opt_loop |
.opt_timestamp: |
lodsb |
cmp al, 10 ; length must be 10 |
jne .no_options |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Got timestamp option\n" |
test [edx + TCP_header.Flags], TH_SYN |
jz @f |
or [ebx + TCP_SOCKET.t_flags], TF_RCVD_TSTMP |
@@: |
lodsd |
bswap eax |
mov [ebx + TCP_SOCKET.ts_val], eax |
lodsd ; timestamp echo reply |
mov [ebx + TCP_SOCKET.ts_ecr], eax |
or [temp_bits], TCP_BIT_TIMESTAMP |
; Since we have a timestamp, lets do the paws test right away! |
test [edx + TCP_header.Flags], TH_RST |
jnz .no_paws |
mov eax, [ebx + TCP_SOCKET.ts_recent] |
test eax, eax |
jz .no_paws |
cmp eax, [ebx + TCP_SOCKET.ts_val] |
jbe .no_paws |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: PAWS: detected an old segment\n" |
mov eax, [timestamp] |
sub eax, [ebx + TCP_SOCKET.ts_recent_age] |
pop ecx |
cmp eax, TCP_PAWS_IDLE |
jle .drop_after_ack ; TODO: update stats |
push ecx |
mov [ebx + TCP_SOCKET.ts_recent], 0 ; timestamp was invalid, fix it. |
.no_paws: |
jmp .opt_loop |
.no_options: |
pop ecx |
;----------------------------------------------------------------------- |
; Time to do some header prediction (Original Principle by Van Jacobson) |
; There are two common cases for an uni-directional data transfer. |
; |
; General rule: the packets has no control flags, is in-sequence, |
; window width didnt change and we're not retransmitting. |
; |
; Second rules: |
; - If the length is 0 and the ACK moved forward, we're the sender side of the transfer. |
; In this case we'll free the ACK'ed data and notify higher levels that we have free space in buffer |
; |
; - If the length is not 0 and the ACK didn't move, we're the receiver side of the transfer. |
; If the packets are in order (data queue is empty), add the data to the socket buffer and request a delayed ACK |
cmp [ebx + TCP_SOCKET.t_state], TCPS_ESTABLISHED |
jnz .not_uni_xfer |
test [edx + TCP_header.Flags], TH_SYN + TH_FIN + TH_RST + TH_URG |
jnz .not_uni_xfer |
test [edx + TCP_header.Flags], TH_ACK |
jz .not_uni_xfer |
mov eax, [edx + TCP_header.SequenceNumber] |
cmp eax, [ebx + TCP_SOCKET.RCV_NXT] |
jne .not_uni_xfer |
mov eax, dword [edx + TCP_header.Window] |
cmp eax, [ebx + TCP_SOCKET.SND_WND] |
jne .not_uni_xfer |
mov eax, [ebx + TCP_SOCKET.SND_NXT] |
cmp eax, [ebx + TCP_SOCKET.SND_MAX] |
jne .not_uni_xfer |
;--------------------------------------- |
; check if we are sender in the uni-xfer |
; If the following 4 conditions are all true, this segment is a pure ACK. |
; |
; - The segment contains no data. |
test ecx, ecx |
jnz .not_sender |
; - The congestion window is greater than or equal to the current send window. |
; This test is true only if the window is fully open, that is, the connection is not in the middle of slow start or congestion avoidance. |
mov eax, [ebx + TCP_SOCKET.SND_CWND] |
cmp eax, [ebx + TCP_SOCKET.SND_WND] |
jb .not_uni_xfer |
; - The acknowledgment field in the segment is less than or equal to the maximum sequence number sent. |
mov eax, [edx + TCP_header.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.SND_MAX] |
ja .not_uni_xfer |
; - The acknowledgment field in the segment is greater than the largest unacknowledged sequence number. |
sub eax, [ebx + TCP_SOCKET.SND_UNA] |
jbe .not_uni_xfer |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Header prediction: we are sender\n" |
;--------------------------------- |
; Packet is a pure ACK, process it |
; Delete acknowledged bytes from send buffer |
pusha |
mov ecx, eax |
lea eax, [ebx + STREAM_SOCKET.snd] |
call SOCKET_ring_free |
popa |
; Update RTT estimators |
test [temp_bits], TCP_BIT_TIMESTAMP |
jz .no_timestamp_rtt |
mov eax, [timestamp] |
sub eax, [ebx + TCP_SOCKET.ts_ecr] |
inc eax |
call TCP_xmit_timer |
jmp .rtt_done |
.no_timestamp_rtt: |
cmp [ebx + TCP_SOCKET.t_rtt], 0 |
je .rtt_done |
mov eax, [edx + TCP_header.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.t_rtseq] |
jbe .rtt_done |
mov eax, [ebx + TCP_SOCKET.t_rtt] |
call TCP_xmit_timer |
.rtt_done: |
; update window pointers |
mov eax, [edx + TCP_header.AckNumber] |
mov [ebx + TCP_SOCKET.SND_UNA], eax |
; Stop retransmit timer |
and [ebx + TCP_SOCKET.timer_flags], not timer_flag_retransmission |
; Unlock the socket |
pusha |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
popa |
; Awaken waiting processes |
mov eax, ebx |
call SOCKET_notify |
; Generate more output |
call TCP_output |
jmp .drop_no_socket |
;------------------------------------------------- |
; maybe we are the receiver in the uni-xfer then.. |
.not_sender: |
; - The amount of data in the segment is greater than 0 (data count is in ecx) |
; - The acknowledgment field equals the largest unacknowledged sequence number. This means no data is acknowledged by this segment. |
mov eax, [edx + TCP_header.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.SND_UNA] |
jne .not_uni_xfer |
; - The reassembly list of out-of-order segments for the connection is empty (seg_next equals tp). |
;;; TODO |
; jnz .not_uni_xfer |
; Complete processing of received data |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Header prediction: we are receiving %u bytes\n", ecx |
mov esi, [dataoffset] |
add esi, edx |
lea eax, [ebx + STREAM_SOCKET.rcv] |
call SOCKET_ring_write ; Add the data to the socket buffer |
add [ebx + TCP_SOCKET.RCV_NXT], ecx ; Update sequence number with number of bytes we have copied |
mov eax, ebx |
call SOCKET_notify |
or [ebx + TCP_SOCKET.t_flags], TF_DELACK ; Set delayed ack flag |
jmp .drop |
;-------------------------------------------------- |
; Header prediction failed, do it the slow way |
.not_uni_xfer: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Header prediction failed\n" |
; Calculate receive window size |
push edx |
mov eax, SOCKET_MAXDATA |
sub eax, [ebx + STREAM_SOCKET.rcv.size] |
DEBUGF DEBUG_NETWORK_VERBOSE, "Space in receive buffer=%d\n", eax |
mov edx, [ebx + TCP_SOCKET.RCV_ADV] |
sub edx, [ebx + TCP_SOCKET.RCV_NXT] |
DEBUGF DEBUG_NETWORK_VERBOSE, "Current advertised window=%d\n", edx |
cmp eax, edx |
jg @f |
mov eax, edx |
@@: |
DEBUGF DEBUG_NETWORK_VERBOSE, "Receive window size=%d\n", eax |
mov [ebx + TCP_SOCKET.RCV_WND], eax |
pop edx |
; If we are in listen or syn_sent state, go to that specific code right away |
cmp [ebx + TCP_SOCKET.t_state], TCPS_LISTEN |
je .LISTEN |
cmp [ebx + TCP_SOCKET.t_state], TCPS_SYN_SENT |
je .SYN_SENT |
;---------------------------- |
; trim any data not in window |
; 1. Check for duplicate data at beginning of segment |
; Calculate number of bytes we need to drop |
mov eax, [ebx + TCP_SOCKET.RCV_NXT] |
sub eax, [edx + TCP_header.SequenceNumber] |
jle .no_duplicate |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: %u bytes duplicate data!\n", eax |
test [edx + TCP_header.Flags], TH_SYN |
jz .no_dup_syn |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: got duplicate syn\n" |
and [edx + TCP_header.Flags], not (TH_SYN) |
inc [edx + TCP_header.SequenceNumber] |
cmp [edx + TCP_header.UrgentPointer], 1 |
jbe @f |
dec [edx + TCP_header.UrgentPointer] |
jmp .dup_syn |
@@: |
and [edx + TCP_header.Flags], not (TH_URG) |
.dup_syn: |
dec eax |
.no_dup_syn: |
; 2. Check for entire duplicate segment |
cmp eax, ecx ; eax holds number of bytes to drop, ecx is data size |
jb .duplicate |
jnz @f |
test [edx + TCP_header.Flags], TH_FIN |
jnz .duplicate |
@@: |
; Any valid FIN must be to the left of the window. |
; At this point the FIN must be out of sequence or a duplicate, drop it |
and [edx + TCP_header.Flags], not TH_FIN |
; send an ACK and resynchronize and drop any data. |
; But keep on processing for RST or ACK |
or [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
mov eax, ecx |
;;; TODO: update stats |
;----------------------------------------------- |
; Remove duplicate data and update urgent offset |
.duplicate: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: trimming duplicate data\n" |
; Trim data from left side of window |
add [dataoffset], eax |
add [edx + TCP_header.SequenceNumber], eax |
sub ecx, eax |
sub [edx + TCP_header.UrgentPointer], ax |
jg @f |
and [edx + TCP_header.Flags], not (TH_URG) |
mov [edx + TCP_header.UrgentPointer], 0 |
@@: |
;-------------------------------------------------- |
; Handle data that arrives after process terminates |
.no_duplicate: |
cmp [ebx + SOCKET.PID], 0 ;;; TODO: use socket flags instead?? |
jne .not_terminated |
cmp [ebx + TCP_SOCKET.t_state], TCPS_CLOSE_WAIT |
jbe .not_terminated |
test ecx, ecx |
jz .not_terminated |
mov eax, ebx |
call TCP_close |
;;; TODO: update stats |
jmp .respond_seg_reset |
;---------------------------------------- |
; Remove data beyond right edge of window |
.not_terminated: |
mov eax, [edx + TCP_header.SequenceNumber] |
add eax, ecx |
sub eax, [ebx + TCP_SOCKET.RCV_NXT] |
sub eax, [ebx + TCP_SOCKET.RCV_WND] ; eax now holds the number of bytes to drop |
jle .no_excess_data |
DEBUGF DEBUG_NETWORK_VERBOSE, "%d bytes beyond right edge of window\n", eax |
;;; TODO: update stats |
cmp eax, ecx |
jl .dont_drop_all |
; If a new connection request is received while in TIME_WAIT, drop the old connection and start over, |
; if the sequence numbers are above the previous ones |
test [edx + TCP_header.Flags], TH_SYN |
jz .no_new_request |
cmp [ebx + TCP_SOCKET.t_state], TCPS_TIMED_WAIT |
jne .no_new_request |
; mov edx, [ebx + TCP_SOCKET.RCV_NXT] |
; cmp edx, [edx + TCP_header.SequenceNumber] |
; add edx, 64000 ; TCP_ISSINCR FIXME |
mov eax, ebx |
call TCP_close |
jmp .findpcb ; FIXME: skip code for unscaling window, ... |
.no_new_request: |
; If window is closed, we can only take segments at window edge, and have to drop data and PUSH from |
; incoming segments. Continue processing, but remember to ACK. Otherwise drop segment and ACK |
cmp [ebx + TCP_SOCKET.RCV_WND], 0 |
jne .drop_after_ack |
mov esi, [edx + TCP_header.SequenceNumber] |
cmp esi, [ebx + TCP_SOCKET.RCV_NXT] |
jne .drop_after_ack |
or [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
;;; TODO: update stats |
.dont_drop_all: |
;;; TODO: update stats |
DEBUGF DEBUG_NETWORK_VERBOSE, "Trimming %u bytes from the right of the window\n" |
sub ecx, eax ; remove data from the right side of window (decrease data length) |
and [edx + TCP_header.Flags], not (TH_PUSH or TH_FIN) |
.no_excess_data: |
;----------------- |
; Record timestamp |
; If last ACK falls within this segments sequence numbers, record its timestamp |
test [temp_bits], TCP_BIT_TIMESTAMP |
jz .no_timestamp |
mov eax, [ebx + TCP_SOCKET.last_ack_sent] |
sub eax, [edx + TCP_header.SequenceNumber] |
jb .no_timestamp |
test [ebx + TCP_header.Flags], TH_SYN or TH_FIN ; syn and fin occupy one byte |
jz @f |
dec eax |
@@: |
sub eax, ecx |
jae .no_timestamp |
DEBUGF DEBUG_NETWORK_VERBOSE, "Recording timestamp\n" |
mov eax, [timestamp] |
mov [ebx + TCP_SOCKET.ts_recent_age], eax |
mov eax, [ebx + TCP_SOCKET.ts_val] |
mov [ebx + TCP_SOCKET.ts_recent], eax |
.no_timestamp: |
;------------------ |
; Process RST flags |
test [edx + TCP_header.Flags], TH_RST |
jz .no_rst |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Got an RST flag\n" |
mov eax, [ebx + TCP_SOCKET.t_state] |
shl eax, 2 |
jmp dword [eax + .rst_sw_list] |
.rst_sw_list: |
dd .no_rst ; TCPS_CLOSED |
dd .no_rst ; TCPS_LISTEN |
dd .no_rst ; TCPS_SYN_SENT |
dd .econnrefused ; TCPS_SYN_RECEIVED |
dd .econnreset ; TCPS_ESTABLISHED |
dd .econnreset ; TCPS_CLOSE_WAIT |
dd .econnreset ; TCPS_FIN_WAIT_1 |
dd .rst_close ; TCPS_CLOSING |
dd .rst_close ; TCPS_LAST_ACK |
dd .econnreset ; TCPS_FIN_WAIT_2 |
dd .rst_close ; TCPS_TIMED_WAIT |
.econnrefused: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Connection refused\n" |
mov [ebx + SOCKET.errorcode], ECONNREFUSED |
jmp .close |
.econnreset: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Connection reset\n" |
mov [ebx + SOCKET.errorcode], ECONNRESET |
.close: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Closing connection\n" |
mov [ebx + TCP_SOCKET.t_state], TCPS_CLOSED |
;;; TODO: update stats (tcp drops) |
mov eax, ebx |
call TCP_close |
jmp .drop_no_socket |
.rst_close: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Closing with reset\n" |
mov eax, ebx |
call TCP_close |
jmp .drop_no_socket |
.no_rst: |
;-------------------------------------- |
; handle SYN-full and ACK-less segments |
test [edx + TCP_header.Flags], TH_SYN |
jz .not_syn_full |
mov eax, ebx |
mov ebx, ECONNRESET |
call TCP_drop |
jmp .drop_with_reset |
.not_syn_full: |
;--------------- |
; ACK processing |
test [edx + TCP_header.Flags], TH_ACK |
jz .drop |
cmp [ebx + TCP_SOCKET.t_state], TCPS_SYN_RECEIVED |
jb .ack_processed ; states: closed, listen, syn_sent |
ja .no_syn_rcv ; established, fin_wait_1, fin_wait_2, close_wait, closing, last_ack, time_wait |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: state=syn_received\n" |
mov eax, [edx + TCP_header.AckNumber] |
cmp [ebx + TCP_SOCKET.SND_UNA], eax |
ja .drop_with_reset |
cmp eax, [ebx + TCP_SOCKET.SND_MAX] |
ja .drop_with_reset |
;;; TODO: update stats |
mov eax, ebx |
call SOCKET_is_connected |
mov [ebx + TCP_SOCKET.t_state], TCPS_ESTABLISHED |
; Do window scaling? |
test [ebx + TCP_SOCKET.t_flags], TF_RCVD_SCALE |
jz @f |
test [ebx + TCP_SOCKET.t_flags], TF_REQ_SCALE |
jz @f |
push word [ebx + TCP_SOCKET.requested_s_scale] ; Set send and receive scale factors to the received values |
pop word [ebx + TCP_SOCKET.SND_SCALE] |
@@: |
;;; TODO: call TCP_reassemble |
mov eax, [edx + TCP_header.SequenceNumber] |
dec eax |
mov [ebx + TCP_SOCKET.SND_WL1], eax |
.no_syn_rcv: |
;------------------------- |
; check for duplicate ACKs |
mov eax, [edx + TCP_header.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.SND_UNA] |
ja .not_dup_ack |
test ecx, ecx |
jnz .reset_dupacks |
mov eax, dword [edx + TCP_header.Window] |
cmp eax, [ebx + TCP_SOCKET.SND_WND] |
jne .reset_dupacks |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Processing duplicate ACK\n" |
; If we have outstanding data, other than a window probe, this is a completely duplicate ACK |
; (window info didnt change) The ACK is the biggest we've seen and we've seen exactly our rexmt threshold of them, |
; assume a packet has been dropped and retransmit it. Kludge snd_nxt & the congestion window so we send only this one packet. |
test [ebx + TCP_SOCKET.timer_flags], timer_flag_retransmission |
jz @f |
mov eax, [edx + TCP_header.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.SND_UNA] |
je .dup_ack |
@@: |
mov [ebx + TCP_SOCKET.t_dupacks], 0 |
jmp .not_dup_ack |
.dup_ack: |
inc [ebx + TCP_SOCKET.t_dupacks] |
cmp [ebx + TCP_SOCKET.t_dupacks], TCP_re_xmit_thresh |
jne .no_re_xmit |
push [ebx + TCP_SOCKET.SND_NXT] ; >>>> |
mov eax, [ebx + TCP_SOCKET.SND_WND] |
cmp eax, [ebx + TCP_SOCKET.SND_CWND] |
jbe @f |
mov eax, [ebx + TCP_SOCKET.SND_CWND] |
@@: |
shr eax, 1 |
push edx |
xor edx, edx |
div [ebx + TCP_SOCKET.t_maxseg] |
cmp eax, 2 |
ja @f |
xor eax, eax |
mov al, 2 |
@@: |
mul [ebx + TCP_SOCKET.t_maxseg] |
pop edx |
mov [ebx + TCP_SOCKET.SND_SSTHRESH], eax |
and [ebx + TCP_SOCKET.timer_flags], not timer_flag_retransmission ; turn off retransmission timer |
mov [ebx + TCP_SOCKET.t_rtt], 0 |
mov eax, [edx + TCP_header.AckNumber] |
mov [ebx + TCP_SOCKET.SND_NXT], eax |
mov eax, [ebx + TCP_SOCKET.t_maxseg] |
mov [ebx + TCP_SOCKET.SND_CWND], eax |
; Unlock the socket |
push ebx |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
; retransmit missing segment |
mov eax, [esp] |
call TCP_output |
; Lock the socket again |
mov ecx, [esp] |
add ecx, SOCKET.mutex |
call mutex_lock |
pop ebx |
; Continue processing |
xor edx, edx |
mov eax, [ebx + TCP_SOCKET.t_maxseg] |
mul [ebx + TCP_SOCKET.t_dupacks] |
add eax, [ebx + TCP_SOCKET.SND_SSTHRESH] |
mov [ebx + TCP_SOCKET.SND_CWND], eax |
pop eax ; <<<< |
cmp eax, [ebx + TCP_SOCKET.SND_NXT] |
jb @f |
mov [ebx + TCP_SOCKET.SND_NXT], eax |
@@: |
jmp .drop |
.no_re_xmit: |
jbe .not_dup_ack |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Increasing congestion window\n" |
mov eax, [ebx + TCP_SOCKET.t_maxseg] |
add [ebx + TCP_SOCKET.SND_CWND], eax |
; Unlock the socket |
push ebx |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
; retransmit missing segment |
mov eax, [esp] |
call TCP_output |
; Lock the socket again |
mov ecx, [esp] |
add ecx, SOCKET.mutex |
call mutex_lock |
pop ebx |
jmp .drop |
.not_dup_ack: |
;------------------------------------------------- |
; If the congestion window was inflated to account |
; for the other side's cached packets, retract it |
mov eax, [ebx + TCP_SOCKET.SND_SSTHRESH] |
cmp eax, [ebx + TCP_SOCKET.SND_CWND] |
ja @f |
cmp [ebx + TCP_SOCKET.t_dupacks], TCP_re_xmit_thresh |
jbe @f |
mov [ebx + TCP_SOCKET.SND_CWND], eax |
@@: |
mov [ebx + TCP_SOCKET.t_dupacks], 0 |
mov eax, [edx + TCP_header.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.SND_MAX] |
jbe @f |
;;; TODO: update stats |
jmp .drop_after_ack |
@@: |
mov edi, [edx + TCP_header.AckNumber] |
sub edi, [ebx + TCP_SOCKET.SND_UNA] ; now we got the number of acked bytes in edi |
;;; TODO: update stats |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: acceptable ACK for %u bytes\n", edi |
;------------------------------------------ |
; RTT measurements and retransmission timer |
; If we have a timestamp, update smoothed RTT |
test [temp_bits], TCP_BIT_TIMESTAMP |
jz .timestamp_not_present |
mov eax, [timestamp] |
sub eax, [ebx + TCP_SOCKET.ts_ecr] |
inc eax |
call TCP_xmit_timer |
jmp .rtt_done_ |
; If no timestamp but transmit timer is running and timed sequence number was acked, |
; update smoothed RTT. Since we now have an RTT measurement, cancel the timer backoff |
; (Phil Karn's retransmit algo) |
; Recompute the initial retransmit timer |
.timestamp_not_present: |
mov eax, [edx + TCP_header.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.t_rtseq] |
jbe .rtt_done_ |
mov eax, [ebx + TCP_SOCKET.t_rtt] |
test eax, eax |
jz .rtt_done_ |
call TCP_xmit_timer |
.rtt_done_: |
; If all outstanding data is acked, stop retransmit timer and remember to restart (more output or persist) |
; If there is more data to be acked, restart retransmit timer, using current (possible backed-off) value. |
mov eax, [ebx + TCP_SOCKET.SND_MAX] |
cmp eax, [edx + TCP_header.AckNumber] |
jne .more_data |
and [ebx + TCP_SOCKET.timer_flags], not timer_flag_retransmission |
or [temp_bits], TCP_BIT_NEEDOUTPUT |
jmp .no_restart |
.more_data: |
test [ebx + TCP_SOCKET.timer_flags], timer_flag_persist |
jnz .no_restart |
mov eax, [ebx + TCP_SOCKET.t_rxtcur] |
mov [ebx + TCP_SOCKET.timer_retransmission], eax |
or [ebx + TCP_SOCKET.timer_flags], timer_flag_retransmission |
.no_restart: |
;------------------------------------------- |
; Open congestion window in response to ACKs |
mov esi, [ebx + TCP_SOCKET.SND_CWND] |
mov eax, [ebx + TCP_SOCKET.t_maxseg] |
cmp esi, [ebx + TCP_SOCKET.SND_SSTHRESH] |
jbe @f |
push edx |
push eax |
mul eax |
div esi |
pop edx |
shr edx, 3 |
add eax, edx |
pop edx |
@@: |
add esi, eax |
push ecx |
mov cl, [ebx + TCP_SOCKET.SND_SCALE] |
mov eax, TCP_max_win |
shl eax, cl |
pop ecx |
cmp esi, eax |
jbe @f |
mov esi, eax |
@@: |
mov [ebx + TCP_SOCKET.SND_CWND], esi |
;------------------------------------------ |
; Remove acknowledged data from send buffer |
cmp edi, [ebx + STREAM_SOCKET.snd.size] |
jbe .finiacked |
push ecx edx ebx |
mov ecx, [ebx + STREAM_SOCKET.snd.size] |
lea eax, [ebx + STREAM_SOCKET.snd] |
sub [ebx + TCP_SOCKET.SND_WND], ecx |
call SOCKET_ring_free |
pop ebx edx ecx |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: our FIN is acked\n" |
stc |
jmp .wakeup |
.finiacked: |
push ecx edx ebx |
mov ecx, edi |
lea eax, [ebx + STREAM_SOCKET.snd] |
call SOCKET_ring_free |
pop ebx |
sub [ebx + TCP_SOCKET.SND_WND], ecx |
pop edx ecx |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: our FIN is not acked\n" |
clc |
;---------------------------------------- |
; Wake up process waiting on send buffer |
.wakeup: |
pushf ; Keep the flags (Carry flag) |
mov eax, ebx |
call SOCKET_notify |
; Update TCPS |
mov eax, [edx + TCP_header.AckNumber] |
mov [ebx + TCP_SOCKET.SND_UNA], eax |
cmp eax, [ebx + TCP_SOCKET.SND_NXT] |
jb @f |
mov [ebx + TCP_SOCKET.SND_NXT], eax |
@@: |
popf |
; General ACK handling complete |
; Now do the state-specific ones |
; Carry flag is set when our FIN is acked |
mov eax, [ebx + TCP_SOCKET.t_state] |
jmp dword [eax*4 + .ACK_sw_list] |
.ACK_sw_list: |
dd .ack_processed ; TCPS_CLOSED |
dd .ack_processed ; TCPS_LISTEN |
dd .ack_processed ; TCPS_SYN_SENT |
dd .ack_processed ; TCPS_SYN_RECEIVED |
dd .ack_processed ; TCPS_ESTABLISHED |
dd .ack_processed ; TCPS_CLOSE_WAIT |
dd .ack_fw1 ; TCPS_FIN_WAIT_1 |
dd .ack_c ; TCPS_CLOSING |
dd .ack_la ; TCPS_LAST_ACK |
dd .ack_processed ; TCPS_FIN_WAIT_2 |
dd .ack_tw ; TCPS_TIMED_WAIT |
.ack_fw1: |
jnc .ack_processed |
test [ebx + SOCKET.state], SS_CANTRCVMORE |
jnz @f |
mov eax, ebx |
call SOCKET_is_disconnected |
mov [ebx + TCP_SOCKET.timer_timed_wait], TCP_time_max_idle |
or [ebx + TCP_SOCKET.timer_flags], timer_flag_wait |
@@: |
mov [ebx + TCP_SOCKET.t_state], TCPS_FIN_WAIT_2 |
jmp .ack_processed |
.ack_c: |
jnc .ack_processed |
mov [ebx + TCP_SOCKET.t_state], TCPS_TIMED_WAIT |
mov eax, ebx |
call TCP_cancel_timers |
mov [ebx + TCP_SOCKET.timer_timed_wait], 2 * TCP_time_MSL |
or [ebx + TCP_SOCKET.timer_flags], timer_flag_wait |
mov eax, ebx |
call SOCKET_is_disconnected |
jmp .ack_processed |
.ack_la: |
jnc .ack_processed |
push ebx |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
pop ebx |
mov eax, ebx |
call TCP_close |
jmp .drop_no_socket |
.ack_tw: |
mov [ebx + TCP_SOCKET.timer_timed_wait], 2 * TCP_time_MSL |
or [ebx + TCP_SOCKET.timer_flags], timer_flag_wait |
jmp .drop_after_ack |
.reset_dupacks: ; We got a new ACK, reset duplicate ACK counter |
mov [ebx + TCP_SOCKET.t_dupacks], 0 |
jmp .ack_processed |
;------- |
; LISTEN |
align 4 |
.LISTEN: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: state=listen\n" |
test [edx + TCP_header.Flags], TH_RST |
jnz .drop |
test [edx + TCP_header.Flags], TH_ACK |
jnz .drop_with_reset |
test [edx + TCP_header.Flags], TH_SYN |
jz .drop |
;;; TODO: check if it's a broadcast or multicast, and drop if so |
push dword [edi] ; Ipv4 source addres |
pop [ebx + IP_SOCKET.RemoteIP] |
push [edx + TCP_header.SourcePort] |
pop [ebx + TCP_SOCKET.RemotePort] |
push [edx + TCP_header.SequenceNumber] |
pop [ebx + TCP_SOCKET.IRS] |
mov eax, [TCP_sequence_num] |
add [TCP_sequence_num], 64000 / 2 |
mov [ebx + TCP_SOCKET.ISS], eax |
mov [ebx + TCP_SOCKET.SND_NXT], eax |
TCP_sendseqinit ebx |
TCP_rcvseqinit ebx |
mov [ebx + TCP_SOCKET.t_state], TCPS_SYN_RECEIVED |
or [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
mov [ebx + TCP_SOCKET.timer_keepalive], TCP_time_keep_interval ;;;; macro |
or [ebx + TCP_SOCKET.timer_flags], timer_flag_keepalive |
lea eax, [ebx + STREAM_SOCKET.snd] |
call SOCKET_ring_create |
lea eax, [ebx + STREAM_SOCKET.rcv] |
call SOCKET_ring_create |
and [temp_bits], not TCP_BIT_DROPSOCKET |
pusha |
mov eax, ebx |
call SOCKET_notify |
popa |
jmp .trim_then_step6 |
;------------ |
; Active Open |
align 4 |
.SYN_SENT: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: state=syn_sent\n" |
test [edx + TCP_header.Flags], TH_ACK |
jz @f |
mov eax, [edx + TCP_header.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.ISS] |
jbe .drop_with_reset |
cmp eax, [ebx + TCP_SOCKET.SND_MAX] |
ja .drop_with_reset |
@@: |
test [edx + TCP_header.Flags], TH_RST |
jz @f |
test [edx + TCP_header.Flags], TH_ACK |
jz .drop |
mov eax, ebx |
mov ebx, ECONNREFUSED |
call TCP_drop |
jmp .drop |
@@: |
test [edx + TCP_header.Flags], TH_SYN |
jz .drop |
; at this point, segment seems to be valid |
test [edx + TCP_header.Flags], TH_ACK |
jz .no_syn_ack |
; now, process received SYN in response to an active open |
mov eax, [edx + TCP_header.AckNumber] |
mov [ebx + TCP_SOCKET.SND_UNA], eax |
cmp eax, [ebx + TCP_SOCKET.SND_NXT] |
jbe @f |
mov [ebx + TCP_SOCKET.SND_NXT], eax |
@@: |
.no_syn_ack: |
and [ebx + TCP_SOCKET.timer_flags], not timer_flag_retransmission ; disable retransmission timer |
push [edx + TCP_header.SequenceNumber] |
pop [ebx + TCP_SOCKET.IRS] |
TCP_rcvseqinit ebx |
or [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
mov eax, [ebx + TCP_SOCKET.SND_UNA] |
cmp eax, [ebx + TCP_SOCKET.ISS] |
jbe .simultaneous_open |
test [edx + TCP_header.Flags], TH_ACK |
jz .simultaneous_open |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: active open\n" |
;;; TODO: update stats |
; set socket state to connected |
push eax |
mov eax, ebx |
call SOCKET_is_connected |
pop eax |
mov [ebx + TCP_SOCKET.t_state], TCPS_ESTABLISHED |
; Do window scaling on this connection ? |
mov eax, [ebx + TCP_SOCKET.t_flags] |
and eax, TF_REQ_SCALE or TF_RCVD_SCALE |
cmp eax, TF_REQ_SCALE or TF_RCVD_SCALE |
jne .no_scaling |
mov ax, word [ebx + TCP_SOCKET.requested_s_scale] |
mov word [ebx + TCP_SOCKET.SND_SCALE], ax |
.no_scaling: |
;;; TODO: reassemble packets queue |
mov eax, [ebx + TCP_SOCKET.t_rtt] |
test eax, eax |
je .trim_then_step6 |
call TCP_xmit_timer |
jmp .trim_then_step6 |
.simultaneous_open: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: simultaneous open\n" |
; We have received a syn but no ACK, so we are having a simultaneous open.. |
mov [ebx + TCP_SOCKET.t_state], TCPS_SYN_RECEIVED |
;------------------------------------- |
; Common processing for receipt of SYN |
.trim_then_step6: |
inc [edx + TCP_header.SequenceNumber] |
; Drop any received data that doesnt fit in the receive window. |
cmp ecx, [ebx + TCP_SOCKET.RCV_WND] |
jbe .dont_trim |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: received data does not fit in window, trimming %u bytes\n", eax |
mov ecx, [ebx + TCP_SOCKET.RCV_WND] |
and [edx + TCP_header.Flags], not (TH_FIN) |
;;; TODO: update stats |
.dont_trim: |
mov eax, [edx + TCP_header.SequenceNumber] |
mov [ebx + TCP_SOCKET.RCV_UP], eax |
dec eax |
mov [ebx + TCP_SOCKET.SND_WL1], eax |
;------- |
; step 6 |
.ack_processed: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: ACK processed\n" |
;---------------------------------------------- |
; check if we need to update window information |
test [edx + TCP_header.Flags], TH_ACK |
jz .no_window_update |
mov eax, [ebx + TCP_SOCKET.SND_WL1] |
cmp eax, [edx + TCP_header.SequenceNumber] |
jb .update_window |
ja @f |
mov eax, [ebx + TCP_SOCKET.SND_WL2] |
cmp eax, [edx + TCP_header.AckNumber] |
jb .update_window |
ja .no_window_update |
@@: |
mov eax, dword [edx + TCP_header.Window] |
cmp eax, [ebx + TCP_SOCKET.SND_WND] |
jbe .no_window_update |
.update_window: |
;;; TODO: update stats (Keep track of pure window updates) |
mov eax, dword [edx + TCP_header.Window] |
cmp eax, [ebx + TCP_SOCKET.max_sndwnd] |
jbe @f |
mov [ebx + TCP_SOCKET.max_sndwnd], eax |
@@: |
mov [ebx + TCP_SOCKET.SND_WND], eax |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Updating window to %u\n", eax |
push [edx + TCP_header.SequenceNumber] |
pop [ebx + TCP_SOCKET.SND_WL1] |
push [edx + TCP_header.AckNumber] |
pop [ebx + TCP_SOCKET.SND_WL2] |
or [temp_bits], TCP_BIT_NEEDOUTPUT |
.no_window_update: |
;----------------- |
; process URG flag |
test [edx + TCP_header.Flags], TH_URG |
jz .not_urgent |
cmp [edx + TCP_header.UrgentPointer], 0 |
jz .not_urgent |
cmp [ebx + TCP_SOCKET.t_state], TCPS_TIMED_WAIT |
je .not_urgent |
; Ignore bogus urgent offsets |
movzx eax, [edx + TCP_header.UrgentPointer] |
add eax, [ebx + STREAM_SOCKET.rcv.size] |
cmp eax, SOCKET_MAXDATA |
jbe .not_urgent |
mov [edx + TCP_header.UrgentPointer], 0 |
and [edx + TCP_header.Flags], not (TH_URG) |
jmp .do_data |
.not_urgent: |
; processing of received urgent pointer |
;;; TODO (1051-1093) |
;--------------------------------------- |
; process the data in the segment (1094) |
.do_data: |
cmp [ebx + TCP_SOCKET.t_state], TCPS_TIMED_WAIT |
jae .final_processing |
test [edx + TCP_header.Flags], TH_FIN |
jnz @f |
test ecx, ecx |
jz .final_processing |
@@: |
; The segment is in order? |
mov eax, [edx + TCP_header.SequenceNumber] |
cmp eax, [ebx + TCP_SOCKET.RCV_NXT] |
jne .out_of_order |
; The reassembly queue is empty? |
cmp [ebx + TCP_SOCKET.seg_next], 0 |
jne .out_of_order |
; The connection is established? |
cmp [ebx + TCP_SOCKET.t_state], TCPS_ESTABLISHED |
jne .out_of_order |
; Ok, lets do this.. Set delayed ACK flag and copy data into socket buffer |
or [ebx + TCP_SOCKET.t_flags], TF_DELACK |
pusha |
mov esi, [dataoffset] |
add esi, edx |
lea eax, [ebx + STREAM_SOCKET.rcv] |
call SOCKET_ring_write ; Add the data to the socket buffer |
add [ebx + TCP_SOCKET.RCV_NXT], ecx ; Update sequence number with number of bytes we have copied |
popa |
; Wake up the sleeping process |
mov eax, ebx |
call SOCKET_notify |
jmp .data_done |
.out_of_order: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP data is out of order!\nSequencenumber is %u, we expected %u.\n", \ |
[edx + TCP_header.SequenceNumber], [ebx + TCP_SOCKET.RCV_NXT] |
; Uh-oh, some data is out of order, lets call TCP reassemble for help |
call TCP_reassemble |
; Generate ACK immediately, to let the other end know that a segment was received out of order, |
; and to tell it what sequence number is expected. This aids the fast-retransmit algorithm. |
or [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
.data_done: |
;--------------- |
; FIN processing |
test [edx + TCP_header.Flags], TH_FIN |
jz .final_processing |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Processing FIN\n" |
cmp [ebx + TCP_SOCKET.t_state], TCPS_TIMED_WAIT |
jae .not_first_fin |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: First FIN for this connection\n" |
mov eax, ebx |
call SOCKET_cant_recv_more |
or [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
inc [ebx + TCP_SOCKET.RCV_NXT] |
.not_first_fin: |
mov eax, [ebx + TCP_SOCKET.t_state] |
shl eax, 2 |
jmp dword [eax + .FIN_sw_list] |
.FIN_sw_list: |
dd .final_processing ; TCPS_CLOSED |
dd .final_processing ; TCPS_LISTEN |
dd .final_processing ; TCPS_SYN_SENT |
dd .fin_syn_est ; TCPS_SYN_RECEIVED |
dd .fin_syn_est ; TCPS_ESTABLISHED |
dd .final_processing ; TCPS_CLOSE_WAIT |
dd .fin_wait1 ; TCPS_FIN_WAIT_1 |
dd .final_processing ; TCPS_CLOSING |
dd .final_processing ; TCPS_LAST_ACK |
dd .fin_wait2 ; TCPS_FIN_WAIT_2 |
dd .fin_timed ; TCPS_TIMED_WAIT |
.fin_syn_est: |
mov [ebx + TCP_SOCKET.t_state], TCPS_CLOSE_WAIT |
jmp .final_processing |
.fin_wait1: |
mov [ebx + TCP_SOCKET.t_state], TCPS_CLOSING |
jmp .final_processing |
.fin_wait2: |
mov [ebx + TCP_SOCKET.t_state], TCPS_TIMED_WAIT |
mov eax, ebx |
call TCP_cancel_timers |
call SOCKET_is_disconnected |
.fin_timed: |
mov [ebx + TCP_SOCKET.timer_timed_wait], 2 * TCP_time_MSL |
or [ebx + TCP_SOCKET.timer_flags], timer_flag_wait |
;----------------- |
; Final processing |
.final_processing: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Final processing\n" |
push ebx |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
pop eax |
test [temp_bits], TCP_BIT_NEEDOUTPUT |
jnz .need_output |
test [eax + TCP_SOCKET.t_flags], TF_ACKNOW |
jz .dumpit |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: ACK now!\n" |
.need_output: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: need output\n" |
call TCP_output |
.dumpit: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: dumping\n" |
call NET_packet_free |
jmp .loop |
;----------------- |
; Drop the segment |
.drop_after_ack: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Drop after ACK\n" |
push edx ebx |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
pop eax edx |
test [edx + TCP_header.Flags], TH_RST |
jnz .dumpit |
or [eax + TCP_SOCKET.t_flags], TF_ACKNOW |
jmp .need_output |
.drop_with_reset: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Drop with reset\n" |
push ebx edx |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
pop edx ebx |
test [edx + TCP_header.Flags], TH_RST |
jnz .dumpit |
;;; if its a multicast/broadcast, also drop |
test [edx + TCP_header.Flags], TH_ACK |
jnz .respond_ack |
test [edx + TCP_header.Flags], TH_SYN |
jnz .respond_syn |
jmp .dumpit |
;--------- |
; Respond |
.respond_ack: |
push ebx |
mov cl, TH_RST |
call TCP_respond |
pop ebx |
jmp .destroy_new_socket |
.respond_syn: |
push ebx |
mov cl, TH_RST + TH_ACK |
call TCP_respond |
pop ebx |
jmp .destroy_new_socket |
.no_socket: |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
.respond_seg_reset: |
test [edx + TCP_header.Flags], TH_RST |
jnz .drop_no_socket |
;;; TODO: if its a multicast/broadcast, also drop |
test [edx + TCP_header.Flags], TH_ACK |
jnz .respond_seg_ack |
test [edx + TCP_header.Flags], TH_SYN |
jnz .respond_seg_syn |
jmp .drop_no_socket |
.respond_seg_ack: |
mov cl, TH_RST |
call TCP_respond_segment |
jmp .drop_no_socket |
.respond_seg_syn: |
mov cl, TH_RST + TH_ACK |
call TCP_respond_segment |
jmp .drop_no_socket |
;----- |
; Drop |
.drop: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Dropping segment\n" |
pusha |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
popa |
.destroy_new_socket: |
test [temp_bits], TCP_BIT_DROPSOCKET |
jz .drop_no_socket |
mov eax, ebx |
call SOCKET_free |
.drop_no_socket: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Drop (no socket)\n" |
call NET_packet_free |
jmp .loop |
endp |
/kernel/branches/kolibri-process/network/tcp_output.inc |
---|
0,0 → 1,657 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; Part of the TCP/IP network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; Based on the code of 4.4BSD ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3289 $ |
;----------------------------------------------------------------- |
; |
; TCP_output |
; |
; IN: eax = socket pointer |
; OUT: eax = 0 on success/errorcode |
; |
;----------------------------------------------------------------- |
align 4 |
proc TCP_output |
locals |
temp_bits db ? |
endl |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_output: socket=%x state=%u\n", eax, [eax + TCP_SOCKET.t_state] |
push eax |
lea ecx, [eax + SOCKET.mutex] |
call mutex_lock |
pop eax |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_output: socket locked\n" |
; We'll detect the length of the data to be transmitted, and flags to be used |
; If there is some data, or any critical controls to send (SYN / RST), then transmit |
; Otherwise, investigate further |
mov ebx, [eax + TCP_SOCKET.SND_MAX] |
cmp ebx, [eax + TCP_SOCKET.SND_UNA] |
jbe .not_idle |
mov ebx, [eax + TCP_SOCKET.t_idle] |
cmp ebx, [eax + TCP_SOCKET.t_rxtcur] |
jbe .not_idle |
; We have been idle for a while and no ACKS are expected to clock out any data we send.. |
; Slow start to get ack "clock" running again. |
mov ebx, [eax + TCP_SOCKET.t_maxseg] |
mov [eax + TCP_SOCKET.SND_CWND], ebx |
.not_idle: |
.again: |
mov [temp_bits], 0 |
mov ebx, [eax + TCP_SOCKET.SND_NXT] ; calculate offset (71) |
sub ebx, [eax + TCP_SOCKET.SND_UNA] ; |
mov ecx, [eax + TCP_SOCKET.SND_WND] ; determine window |
cmp ecx, [eax + TCP_SOCKET.SND_CWND] ; |
jb @f ; |
mov ecx, [eax + TCP_SOCKET.SND_CWND] ; |
@@: ; |
call TCP_outflags ; flags in dl |
;------------------------ |
; data being forced out ? |
; If in persist timeout with window of 0, send 1 byte. |
; Otherwise, if window is small but nonzero, and timer expired, |
; we will send what we can and go to transmit state |
cmp [eax + TCP_SOCKET.t_force], 0 |
je .no_force |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_output: forcing data out\n" |
test ecx, ecx |
jnz .no_zero_window |
cmp ebx, [eax + STREAM_SOCKET.snd.size] |
jae @f |
and dl, not (TH_FIN) |
@@: |
inc ecx |
jmp .no_force |
.no_zero_window: |
and [eax + TCP_SOCKET.timer_flags], not timer_flag_persist |
mov [eax + TCP_SOCKET.t_rxtshift], 0 |
.no_force: |
;-------------------------------- |
; Calculate how much data to send (106) |
mov esi, [eax + STREAM_SOCKET.snd.size] |
cmp esi, ecx |
jb @f |
mov esi, ecx |
@@: |
sub esi, ebx |
;------------------------ |
; check for window shrink (107) |
; If FIN has been set, but not ACKed, but we havent been called to retransmit, esi will be -1 |
; Otherwise, window shrank after we sent into it. |
jae .not_persist |
; enter persist state |
xor esi, esi |
; If window shrank to 0 |
test ecx, ecx |
jnz @f |
; cancel pending retransmit |
and [eax + TCP_SOCKET.timer_flags], not timer_flag_retransmission |
; pull SND_NXT back to (closed) window, We will enter persist state below. |
push [eax + TCP_SOCKET.SND_UNA] |
pop [eax + TCP_SOCKET.SND_NXT] |
@@: |
; If window didn't close completely, just wait for an ACK |
.not_persist: |
;--------------------------- |
; Send one segment at a time (124) |
cmp esi, [eax + TCP_SOCKET.t_maxseg] |
jbe @f |
mov esi, [eax + TCP_SOCKET.t_maxseg] |
or [temp_bits], TCP_BIT_SENDALOT |
@@: |
;-------------------------------------------- |
; Turn of FIN flag if send buffer not emptied (128) |
mov edi, [eax + TCP_SOCKET.SND_NXT] |
add edi, esi |
sub edi, [eax + TCP_SOCKET.SND_UNA] |
cmp edi, [eax + STREAM_SOCKET.snd.size] |
jae @f |
and dl, not (TH_FIN) |
@@: |
;------------------------------- |
; calculate window advertisement (130) |
mov ecx, SOCKET_MAXDATA |
sub ecx, [eax + STREAM_SOCKET.rcv.size] |
;------------------------------ |
; Sender silly window avoidance (131) |
test esi, esi |
jz .len_zero |
cmp esi, [eax + TCP_SOCKET.t_maxseg] |
je .send |
add ebx, esi ; offset + length |
cmp ebx, [eax + STREAM_SOCKET.snd.size] |
jb @f |
test [eax + TCP_SOCKET.t_flags], TF_NODELAY |
jnz .send |
mov ebx, [eax + TCP_SOCKET.SND_MAX] |
cmp ebx, [eax + TCP_SOCKET.SND_UNA] |
je .send |
@@: |
test [eax + TCP_SOCKET.t_force], -1 ;;; |
jnz .send |
mov ebx, [eax + TCP_SOCKET.max_sndwnd] |
shr ebx, 1 |
cmp esi, ebx |
jae .send |
mov ebx, [eax + TCP_SOCKET.SND_NXT] |
cmp ebx, [eax + TCP_SOCKET.SND_MAX] |
jb .send |
.len_zero: |
;---------------------------------------- |
; Check if a window update should be sent (154) |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_output: window=%d\n", ecx |
; Compare available window to amount of window known to peer (as advertised window less next expected input) |
; If the difference is at least two max size segments, or at least 50% of the maximum possible window, |
; Then we want to send a window update to the peer. |
test ecx, ecx |
jz .no_window |
push ecx |
mov cl, [eax + TCP_SOCKET.RCV_SCALE] |
mov ebx, TCP_max_win |
shl ebx, cl |
pop ecx |
cmp ebx, ecx |
jb @f |
mov ebx, ecx |
@@: |
sub ebx, [eax + TCP_SOCKET.RCV_ADV] |
add ebx, [eax + TCP_SOCKET.RCV_NXT] |
mov edi, [eax + TCP_SOCKET.t_maxseg] |
shl edi, 1 |
cmp ebx, edi |
jae .send |
shl ebx, 1 |
; cmp ebx, [eax + TCP_SOCKET.] ;;; TODO: check with receive buffer high water mark |
; jae TCP_send |
.no_window: |
;-------------------------- |
; Should a segment be sent? (174) |
test [eax + TCP_SOCKET.t_flags], TF_ACKNOW ; we need to ACK |
jnz .send |
test dl, TH_SYN + TH_RST ; we need to send a SYN or RST |
jnz .send |
mov ebx, [eax + TCP_SOCKET.SND_UP] ; when urgent pointer is beyond start of send bufer |
cmp ebx, [eax + TCP_SOCKET.SND_UNA] |
ja .send |
test dl, TH_FIN |
jz .enter_persist ; no reason to send, enter persist state |
; FIN was set, only send if not already sent, or on retransmit |
test [eax + TCP_SOCKET.t_flags], TF_SENTFIN |
jz .send |
mov ebx, [eax + TCP_SOCKET.SND_NXT] |
cmp ebx, [eax + TCP_SOCKET.SND_UNA] |
je .send |
;-------------------- |
; Enter persist state (191) |
.enter_persist: |
cmp [eax + STREAM_SOCKET.snd.size], 0 ; Data ready to send? |
jne @f |
and [eax + TCP_SOCKET.timer_flags], not timer_flag_retransmission |
jne @f |
test [eax + TCP_SOCKET.timer_flags], timer_flag_persist ; Persist timer already expired? |
jnz @f |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_output: Entering persist state\n" |
mov [eax + TCP_SOCKET.t_rxtshift], 0 |
call TCP_set_persist |
@@: |
;---------------------------- |
; No reason to send a segment (219) |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_output: No reason to send a segment\n" |
pusha |
lea ecx, [eax + SOCKET.mutex] |
call mutex_unlock |
popa |
; Fixme: returnvalue? |
ret |
;----------------------------------------------- |
; |
; Send a segment (222) |
; |
; eax = socket pointer |
; esi = data len |
; dl = flags |
; |
;----------------------------------------------- |
.send: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: socket=%x length=%u flags=%x\n", eax, esi, dl |
push eax ; save socket ptr |
push esi ; and data length too |
mov edi, sizeof.TCP_header ; edi will contain headersize |
;------------------------------------ |
; Send options with first SYN segment |
test dl, TH_SYN |
jz .options_done |
push [eax + TCP_SOCKET.ISS] |
pop [eax + TCP_SOCKET.SND_NXT] |
test [eax + TCP_SOCKET.t_flags], TF_NOOPT |
jnz .options_done |
mov ecx, 1460 ;;;; FIXME: use routing blablabla to determine MSS |
or ecx, TCP_OPT_MAXSEG shl 24 + 4 shl 16 |
bswap ecx |
push ecx |
add di, 4 |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: added maxseg option\n" |
test [eax + TCP_SOCKET.t_flags], TF_REQ_SCALE |
jz .no_scale |
test dl, TH_ACK |
jz .scale_opt |
test [eax + TCP_SOCKET.t_flags], TF_RCVD_SCALE |
jz .no_scale |
.scale_opt: |
mov cl, [eax + TCP_SOCKET.request_r_scale] |
mov ch, TCP_OPT_NOP |
pushw cx |
pushw TCP_OPT_WINDOW + 3 shl 8 |
add di, 4 |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: added scale option\n" |
.no_scale: |
.no_syn: |
;------------------------------------ |
; Make the timestamp option if needed |
test [eax + TCP_SOCKET.t_flags], TF_REQ_TSTMP |
jz .no_timestamp |
test dl, TH_RST |
jnz .no_timestamp |
test dl, TH_ACK |
jz .timestamp |
test [eax + TCP_SOCKET.t_flags], TF_RCVD_TSTMP |
jz .no_timestamp |
.timestamp: |
pushd 0 |
pushd [timer_ticks] |
pushd TCP_OPT_NOP + TCP_OPT_NOP shl 8 + TCP_OPT_TIMESTAMP shl 16 + 10 shl 24 |
add di, 12 |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: added timestamp\n" |
.no_timestamp: |
; <Add additional options here> |
.options_done: |
; eax = socket ptr |
; edx = flags |
; edi = header size |
; esi = data len |
;--------------------------------------------- |
; check if we dont exceed the max segment size (270) |
add esi, edi ; total TCP segment size |
cmp esi, [eax + TCP_SOCKET.t_maxseg] |
jbe .no_overflow |
mov esi, [eax + TCP_SOCKET.t_maxseg] |
or [temp_bits], TCP_BIT_SENDALOT |
.no_overflow: |
;---------------------------------------------------- |
; Calculate the receive window. |
; Dont shrink window, but avoid silly window syndrome |
mov ebx, SOCKET_MAXDATA |
sub ebx, [eax + STREAM_SOCKET.rcv.size] |
cmp ebx, SOCKET_MAXDATA/4 |
jae @f |
cmp ebx, [eax + TCP_SOCKET.t_maxseg] |
jae @f |
xor ebx, ebx |
@@: |
cmp ebx, TCP_max_win |
jbe @f |
mov ebx, TCP_max_win |
@@: |
mov ecx, [eax + TCP_SOCKET.RCV_ADV] |
sub ecx, [eax + TCP_SOCKET.RCV_NXT] |
cmp ebx, ecx |
ja @f |
mov ebx, ecx |
@@: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: window = %u\n", ebx |
mov cl, [eax + TCP_SOCKET.RCV_SCALE] |
shr ebx, cl |
xchg bl, bh |
;----------------------------------------------------------------- |
; Start by pushing all TCP header values in reverse order on stack |
; (essentially, creating the tcp header on the stack!) |
pushw 0 ; .UrgentPointer dw ? |
pushw 0 ; .Checksum dw ? |
pushw bx ; .Window dw ? |
shl edi, 2 ; .DataOffset db ? only 4 left-most bits |
shl dx, 8 |
or dx, di ; .Flags db ? |
pushw dx |
shr edi, 2 ; .DataOffset db ? |
push [eax + TCP_SOCKET.RCV_NXT] ; .AckNumber dd ? |
ntohd [esp] |
push [eax + TCP_SOCKET.SND_NXT] ; .SequenceNumber dd ? |
ntohd [esp] |
push [eax + TCP_SOCKET.RemotePort] ; .DestinationPort dw ? |
push [eax + TCP_SOCKET.LocalPort] ; .SourcePort dw ? |
push edi ; header size |
;--------------------- |
; Create the IP packet |
mov ecx, esi |
mov edx, [eax + IP_SOCKET.LocalIP] ; source ip |
mov eax, [eax + IP_SOCKET.RemoteIP] ; dest ip |
mov di, IP_PROTO_TCP shl 8 + 128 |
call IPv4_output |
jz .ip_error |
;----------------------------------------- |
; Move TCP header from stack to TCP packet |
push ecx |
mov ecx, [esp + 4] |
lea esi, [esp + 8] |
shr ecx, 2 ; count is in bytes, we will work with dwords |
rep movsd |
pop ecx ; full TCP packet size |
pop esi ; headersize |
add esp, esi ; remove it from stack |
push edx ; packet size for send proc |
push eax ; packet ptr for send proc |
mov edx, edi ; begin of data |
sub edx, esi ; begin of packet (edi = begin of data) |
push ecx |
sub ecx, esi ; data size |
;-------------- |
; Copy the data |
; eax = ptr to ring struct |
; ecx = buffer size |
; edi = ptr to buffer |
mov eax, [esp + 16] ; get socket ptr |
push edx |
push [eax + TCP_SOCKET.SND_NXT] ; we'll need this for timing the transmission |
test ecx, ecx |
jz .nodata |
mov edx, [eax + TCP_SOCKET.SND_NXT] |
add [eax + TCP_SOCKET.SND_NXT], ecx ; update sequence number <<< CHECKME |
sub edx, [eax + TCP_SOCKET.SND_UNA] ; offset |
add eax, STREAM_SOCKET.snd |
call SOCKET_ring_read |
.nodata: |
pop edi |
pop esi ; begin of data |
pop ecx ; full packet size |
mov eax, [esp + 12] ; socket ptr |
;---------------------------------- |
; initialize retransmit timer (400) |
;TODO: check t_force and persist |
test [esi + TCP_header.Flags], TH_SYN + TH_FIN ; syn and fin take a sequence number |
jz @f |
inc [eax + TCP_SOCKET.SND_NXT] |
test [esi + TCP_header.Flags], TH_FIN |
jz @f |
or [eax + TCP_SOCKET.t_flags], TF_SENTFIN ; if we sent a fin, set the sentfin flag |
@@: |
mov edx, [eax + TCP_SOCKET.SND_NXT] |
cmp edx, [eax + TCP_SOCKET.SND_MAX] ; is this a retransmission? |
jbe @f |
mov [eax + TCP_SOCKET.SND_MAX], edx ; [eax + TCP_SOCKET.SND_NXT] from before we updated it |
cmp [eax + TCP_SOCKET.t_rtt], 0 ; are we currently timing anything? |
je @f |
mov [eax + TCP_SOCKET.t_rtt], 1 ; nope, start transmission timer |
mov [eax + TCP_SOCKET.t_rtseq], edi |
;TODO: update stats |
@@: |
; set retransmission timer if not already set, and not doing an ACK or keepalive probe |
test [eax + TCP_SOCKET.timer_flags], timer_flag_retransmission |
jnz .retransmit_set |
cmp edx, [eax + TCP_SOCKET.SND_UNA] ; edx is still [eax + TCP_SOCKET.SND_NXT] |
je .retransmit_set |
mov edx, [eax + TCP_SOCKET.t_rxtcur] |
mov [eax + TCP_SOCKET.timer_retransmission], edx |
or [eax + TCP_SOCKET.timer_flags], timer_flag_retransmission |
test [eax + TCP_SOCKET.timer_flags], timer_flag_persist |
jz .retransmit_set |
and [eax + TCP_SOCKET.timer_flags], not timer_flag_persist |
mov [eax + TCP_SOCKET.t_rxtshift], 0 |
.retransmit_set: |
;-------------------- |
; Create the checksum |
xor dx, dx |
test [ebx + NET_DEVICE.hwacc], NET_HWACC_TCP_IPv4_OUT |
jnz .checksum_ok |
TCP_checksum (eax + IP_SOCKET.LocalIP), (eax + IP_SOCKET.RemoteIP) |
.checksum_ok: |
mov [esi + TCP_header.Checksum], dx |
;---------------- |
; Send the packet |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: Sending with device %x\n", ebx |
call [ebx + NET_DEVICE.transmit] |
jnz .send_error |
;--------------- |
; Ok, data sent! |
pop ecx |
pop eax |
call NET_ptr_to_num4 |
inc [TCP_segments_tx + edi] |
; update advertised receive window |
test ecx, ecx |
jz @f |
add ecx, [eax + TCP_SOCKET.RCV_NXT] |
cmp ecx, [eax + TCP_SOCKET.RCV_ADV] |
jbe @f |
mov [eax + TCP_SOCKET.RCV_ADV], ecx |
@@: |
; update last ack sent |
push [eax + TCP_SOCKET.RCV_NXT] |
pop [eax + TCP_SOCKET.last_ack_sent] |
; clear the ACK flags |
and [eax + TCP_SOCKET.t_flags], not (TF_ACKNOW + TF_DELACK) |
;-------------- |
; unlock socket |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: unlocking socket 0x%x\n", eax |
push eax |
lea ecx, [eax + SOCKET.mutex] |
call mutex_unlock |
pop eax |
;----------------------------- |
; Check if we need more output |
test [temp_bits], TCP_BIT_SENDALOT |
jnz TCP_output.again |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: success!\n" |
xor eax, eax |
ret |
.ip_error: |
pop ecx |
add esp, ecx |
add esp, 4 |
pop eax |
mov [eax + TCP_SOCKET.timer_retransmission], TCP_time_re_min |
or [eax + TCP_SOCKET.timer_flags], timer_flag_retransmission |
lea ecx, [eax + SOCKET.mutex] |
call mutex_unlock |
DEBUGF DEBUG_NETWORK_ERROR, "TCP_send: IP error\n" |
or eax, -1 |
ret |
.send_error: |
add esp, 4 |
pop eax |
lea ecx, [eax + SOCKET.mutex] |
call mutex_unlock |
DEBUGF DEBUG_NETWORK_ERROR, "TCP_send: sending failed\n" |
or eax, -2 |
ret |
endp |
/kernel/branches/kolibri-process/network/tcp_subr.inc |
---|
0,0 → 1,588 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; Part of the TCP/IP network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; Based on the code of 4.4BSD ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3514 $ |
align 4 |
iglobal |
TCP_backoff db 0,1,2,3,4,5,6,6,6,6,6,6,6 |
endg |
macro TCP_checksum IP1, IP2 { |
;------------- |
; Pseudoheader |
; protocol type |
mov edx, IP_PROTO_TCP |
; source address |
add dl, byte [IP1+1] |
adc dh, byte [IP1+0] |
adc dl, byte [IP1+3] |
adc dh, byte [IP1+2] |
; destination address |
adc dl, byte [IP2+1] |
adc dh, byte [IP2+0] |
adc dl, byte [IP2+3] |
adc dh, byte [IP2+2] |
; size |
adc dl, cl |
adc dh, ch |
adc edx, 0 |
;--------------------- |
; Real header and data |
push esi |
call checksum_1 |
call checksum_2 |
pop esi |
} ; returns in dx only |
macro TCP_sendseqinit ptr { |
push edi ;;;; i dont like this static use of edi |
mov edi, [ptr + TCP_SOCKET.ISS] |
mov [ptr + TCP_SOCKET.SND_UP], edi |
mov [ptr + TCP_SOCKET.SND_MAX], edi |
mov [ptr + TCP_SOCKET.SND_NXT], edi |
mov [ptr + TCP_SOCKET.SND_UNA], edi |
pop edi |
} |
macro TCP_rcvseqinit ptr { |
push edi |
mov edi, [ptr + TCP_SOCKET.IRS] |
inc edi |
mov [ptr + TCP_SOCKET.RCV_NXT], edi |
mov [ptr + TCP_SOCKET.RCV_ADV], edi |
pop edi |
} |
macro TCP_init_socket socket { |
mov [socket + TCP_SOCKET.t_maxseg], TCP_mss_default |
mov [socket + TCP_SOCKET.t_flags], TF_REQ_SCALE or TF_REQ_TSTMP |
mov [socket + TCP_SOCKET.t_srtt], TCP_time_srtt_default |
mov [socket + TCP_SOCKET.t_rttvar], TCP_time_rtt_default * 4 |
mov [socket + TCP_SOCKET.t_rttmin], TCP_time_re_min |
;;; TODO: TCP_time_rangeset |
mov [socket + TCP_SOCKET.SND_CWND], TCP_max_win shl TCP_max_winshift |
mov [socket + TCP_SOCKET.SND_SSTHRESH], TCP_max_win shl TCP_max_winshift |
} |
;--------------------------- |
; |
; TCP_pull_out_of_band |
; |
; IN: eax = |
; ebx = socket ptr |
; edx = tcp packet ptr |
; |
; OUT: / |
; |
;--------------------------- |
align 4 |
TCP_pull_out_of_band: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_pull_out_of_band\n" |
;;;; 1282-1305 |
ret |
;------------------------- |
; |
; TCP_drop |
; |
; IN: eax = socket ptr |
; ebx = error number |
; |
; OUT: eax = socket ptr |
; |
;------------------------- |
align 4 |
TCP_drop: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_drop: %x\n", eax |
cmp [eax + TCP_SOCKET.t_state], TCPS_SYN_RECEIVED |
jb .no_syn_received |
mov [eax + TCP_SOCKET.t_state], TCPS_CLOSED |
push eax |
call TCP_output |
pop eax |
;;; TODO: update stats |
jmp TCP_close |
.no_syn_received: |
;;; TODO: update stats |
;;; TODO: check if error code is "Connection timed out' and handle accordingly |
; mov [eax + SOCKET.errorcode], ebx |
;------------------------- |
; |
; TCP_disconnect |
; |
; IN: eax = socket ptr |
; OUT: eax = socket ptr / 0 |
; |
;------------------------- |
align 4 |
TCP_disconnect: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_disconnect: %x\n", eax |
cmp [eax + TCP_SOCKET.t_state], TCPS_ESTABLISHED |
jb TCP_close ; Connection not yet synchronised, just get rid of the socket |
; TODO: implement LINGER |
call SOCKET_is_disconnecting |
call TCP_usrclosed |
test eax, eax |
jz @f |
push eax |
call TCP_output |
pop eax |
@@: |
ret |
;------------------------- |
; |
; TCP_close |
; |
; IN: eax = socket ptr |
; OUT: / |
; |
;------------------------- |
align 4 |
TCP_close: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_close: %x\n", eax |
;;; TODO: update RTT and mean deviation |
;;; TODO: update slow start threshold |
call SOCKET_is_disconnected |
call SOCKET_free |
xor eax, eax |
ret |
;------------------------- |
; |
; TCP_outflags |
; |
; IN: eax = socket ptr |
; |
; OUT: edx = flags |
; |
;------------------------- |
align 4 |
TCP_outflags: |
mov edx, [eax + TCP_SOCKET.t_state] |
movzx edx, byte [edx + .flaglist] |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_outflags: socket=%x flags=%x\n", eax, dl |
ret |
.flaglist: |
db TH_RST + TH_ACK ; TCPS_CLOSED |
db 0 ; TCPS_LISTEN |
db TH_SYN ; TCPS_SYN_SENT |
db TH_SYN + TH_ACK ; TCPS_SYN_RECEIVED |
db TH_ACK ; TCPS_ESTABLISHED |
db TH_ACK ; TCPS_CLOSE_WAIT |
db TH_FIN + TH_ACK ; TCPS_FIN_WAIT_1 |
db TH_FIN + TH_ACK ; TCPS_CLOSING |
db TH_FIN + TH_ACK ; TCPS_LAST_ACK |
db TH_ACK ; TCPS_FIN_WAIT_2 |
db TH_ACK ; TCPS_TIMED_WAIT |
;--------------------------------------- |
; |
; The fast way to send an ACK/RST/keepalive segment |
; |
; TCP_respond |
; |
; IN: ebx = socket ptr |
; cl = flags |
; |
;-------------------------------------- |
align 4 |
TCP_respond: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_respond_socket: socket=%x flags=%x\n", ebx, cl |
;--------------------- |
; Create the IP packet |
push cx ebx |
mov eax, [ebx + IP_SOCKET.RemoteIP] |
mov edx, [ebx + IP_SOCKET.LocalIP] |
mov ecx, sizeof.TCP_header |
mov di, IP_PROTO_TCP shl 8 + 128 |
call IPv4_output |
test edi, edi |
jz .error |
pop esi cx |
push edx eax |
;----------------------------------------------- |
; Fill in the TCP header by using the socket ptr |
mov ax, [esi + TCP_SOCKET.LocalPort] |
stosw |
mov ax, [esi + TCP_SOCKET.RemotePort] |
stosw |
mov eax, [esi + TCP_SOCKET.SND_NXT] |
bswap eax |
stosd |
mov eax, [esi + TCP_SOCKET.RCV_NXT] |
bswap eax |
stosd |
mov al, 0x50 ; Dataoffset: 20 bytes (TCP_header.DataOffset) |
stosb |
mov al, cl |
stosb |
; mov ax, [esi + TCP_SOCKET.RCV_WND] |
; rol ax, 8 |
mov ax, 0x00a0 ;;;;;;; FIXME |
stosw ; window |
xor eax, eax |
stosd ; checksum + urgentpointer |
;--------------------- |
; Fill in the checksum |
.checksum: |
sub edi, sizeof.TCP_header |
mov ecx, sizeof.TCP_header |
xchg esi, edi |
TCP_checksum (edi + IP_SOCKET.LocalIP), (edi + IP_SOCKET.RemoteIP) |
mov [esi+TCP_header.Checksum], dx |
;-------------------- |
; And send the segment |
call [ebx + NET_DEVICE.transmit] |
test eax, eax |
jnz @f |
call NET_ptr_to_num4 |
inc [TCP_segments_tx + edi] |
@@: |
ret |
.error: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_respond_socket: failed\n" |
add esp, 2 + 4 |
ret |
;------------------------- |
; TCP_respond_segment: |
; |
; IN: edx = segment ptr (a previously received segment) |
; edi = ptr to dest and src IPv4 addresses |
; cl = flags |
align 4 |
TCP_respond_segment: |
DEBUGF DEBUG_NETWORK_VERBOSE,"TCP_respond_segment: frame=%x flags=%x\n", edx, cl |
;--------------------- |
; Create the IP packet |
push cx edx |
mov edx, [edi + 4] |
mov eax, [edi] |
mov ecx, sizeof.TCP_header |
mov di, IP_PROTO_TCP shl 8 + 128 |
call IPv4_output |
jz .error |
pop esi cx |
push edx eax |
;--------------------------------------------------- |
; Fill in the TCP header by using a received segment |
mov ax, [esi + TCP_header.DestinationPort] |
stosw |
mov ax, [esi + TCP_header.SourcePort] |
stosw |
mov eax, [esi + TCP_header.AckNumber] |
bswap eax |
stosd |
xor eax, eax |
stosd |
mov al, 0x50 ; Dataoffset: 20 bytes (sizeof.TCP_header/4 shl 4) |
stosb |
mov al, cl |
stosb |
mov ax, 1280 |
rol ax, 8 |
stosw ; window |
xor eax, eax |
stosd ; checksum + urgentpointer |
;--------------------- |
; Fill in the checksum |
lea esi, [edi - sizeof.TCP_header] |
mov ecx, sizeof.TCP_header |
TCP_checksum (esi - sizeof.IPv4_header + IPv4_header.DestinationAddress),\ ; FIXME |
(esi - sizeof.IPv4_header + IPv4_header.SourceAddress) |
mov [esi + TCP_header.Checksum], dx |
;-------------------- |
; And send the segment |
call [ebx + NET_DEVICE.transmit] |
test eax, eax |
jnz @f |
call NET_ptr_to_num4 |
inc [TCP_segments_tx + edi] |
@@: |
ret |
.error: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_respond_segment: failed\n" |
add esp, 2+4 |
ret |
macro TCPT_RANGESET timer, value, min, max { |
local .min |
local .max |
local .done |
cmp value, min |
jb .min |
cmp value, max |
ja .max |
mov timer, value |
jmp .done |
.min: |
mov timer, value |
jmp .done |
.max: |
mov timer, value |
jmp .done |
.done: |
} |
align 4 |
TCP_set_persist: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_set_persist\n" |
; First, check if retransmit timer is not set, retransmit and persist are mutually exclusive |
test [eax + TCP_SOCKET.timer_flags], timer_flag_retransmission |
jnz .exit |
; calculate RTO |
push ebx |
mov ebx, [eax + TCP_SOCKET.t_srtt] |
shr ebx, 2 |
add ebx, [eax + TCP_SOCKET.t_rttvar] |
shr ebx, 1 |
mov cl, [eax + TCP_SOCKET.t_rxtshift] |
shl ebx, cl |
; Start/restart persistance timer. |
TCPT_RANGESET [eax + TCP_SOCKET.timer_persist], ebx, TCP_time_pers_min, TCP_time_pers_max |
or [ebx + TCP_SOCKET.timer_flags], timer_flag_persist |
pop ebx |
cmp [eax + TCP_SOCKET.t_rxtshift], TCP_max_rxtshift |
jae @f |
inc [eax + TCP_SOCKET.t_rxtshift] |
@@: |
.exit: |
ret |
; eax = rtt |
; ebx = socket ptr |
align 4 |
TCP_xmit_timer: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_xmit_timer: socket=%x rtt=%d0ms\n", ebx, eax |
;TODO: update stats |
cmp [ebx + TCP_SOCKET.t_rtt], 0 |
je .no_rtt_yet |
; srtt is stored as a fixed point with 3 bits after the binary point. |
; The following magic is equivalent of the smoothing algorithm in rfc793 with an alpha of .875 |
; (srtt = rtt/8 + srtt*7/8 in fixed point) |
; Adjust rtt to origin 0. |
push ecx |
mov ecx, [ebx + TCP_SOCKET.t_srtt] |
shr ecx, TCP_RTT_SHIFT |
sub eax, ecx |
dec eax |
pop ecx |
add [ebx + TCP_SOCKET.t_srtt], eax |
ja @f |
mov [ebx + TCP_SOCKET.t_srtt], 1 |
@@: |
; We accumulate a smoothed rtt variance (actually, a smoothed mean difference), |
; then set the retransmit timer to smoothed rtt + 4 times the smoothed variance. |
; rttvar is stored as fixed point with 2 bits after the binary point. |
; The following is equivalent to rfc793 smoothing with an alpha of .75 |
; (rttvar = rttvar*3/4 + delta/4) (delta = eax) |
; get abs(eax) |
push edx |
cdq |
xor eax, edx |
sub eax, edx |
mov edx, [ebx + TCP_SOCKET.t_rttvar] |
shr edx, TCP_RTTVAR_SHIFT |
sub eax, edx |
pop edx |
add [ebx + TCP_SOCKET.t_rttvar], eax |
ja @f |
mov [ebx + TCP_SOCKET.t_rttvar], 1 |
@@: |
ret |
.no_rtt_yet: |
push ecx |
mov ecx, eax |
shl ecx, TCP_RTT_SHIFT |
mov [ebx + TCP_SOCKET.t_srtt], ecx |
shl eax, TCP_RTTVAR_SHIFT - 1 |
mov [ebx + TCP_SOCKET.t_rttvar], eax |
pop ecx |
ret |
; eax = max segment size |
; ebx = socket ptr |
align 4 |
TCP_mss: |
cmp eax, 1420 ; FIXME |
jbe @f |
mov eax, 1420 |
@@: |
mov [ebx + TCP_SOCKET.t_maxseg], eax |
ret |
; ebx = socket ptr |
; edx = segment ptr |
align 4 |
TCP_reassemble: |
ret |
/kernel/branches/kolibri-process/network/tcp_timer.inc |
---|
0,0 → 1,172 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; Part of the TCP/IP network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; Based on the code of 4.4BSD ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 3143 $ |
timer_flag_retransmission = 1 shl 0 |
timer_flag_keepalive = 1 shl 1 |
timer_flag_2msl = 1 shl 2 |
timer_flag_persist = 1 shl 3 |
timer_flag_wait = 1 shl 4 |
;---------------------- |
; 160 ms timer |
;---------------------- |
macro TCP_timer_160ms { |
local .loop |
local .exit |
mov ebx, net_sockets |
.loop: |
mov ebx, [ebx + SOCKET.NextPtr] |
test ebx, ebx |
jz .exit |
cmp [ebx + SOCKET.Domain], AF_INET4 |
jne .loop |
cmp [ebx + SOCKET.Protocol], IP_PROTO_TCP |
jne .loop |
test [ebx + TCP_SOCKET.t_flags], TF_DELACK |
jz .loop |
and [ebx + TCP_SOCKET.t_flags], not (TF_DELACK) |
push ebx |
mov cl, TH_ACK |
call TCP_respond |
; and [ebx + TCP_SOCKET.t_flags], TF_ACKNOW ;; |
; mov eax, ebx ;; |
; call TCP_output ;; |
pop ebx |
jmp .loop |
.exit: |
} |
;---------------------- |
; 640 ms timer |
;---------------------- |
macro TCP_timer_640ms { ; TODO: implement timed wait timer! |
local .loop |
local .exit |
; Update TCP sequence number |
add [TCP_sequence_num], 64000 |
; scan through all the active TCP sockets, decrementing ALL timers |
; When a timer reaches zero, we'll check wheter it was active or not |
mov eax, net_sockets |
.loop: |
mov eax, [eax + SOCKET.NextPtr] |
.check_only: |
or eax, eax |
jz .exit |
cmp [eax + SOCKET.Domain], AF_INET4 |
jne .loop |
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP |
jne .loop |
inc [eax + TCP_SOCKET.t_idle] |
dec [eax + TCP_SOCKET.timer_retransmission] |
jnz .check_more2 |
test [eax + TCP_SOCKET.timer_flags], timer_flag_retransmission |
jz .check_more2 |
DEBUGF DEBUG_NETWORK_VERBOSE, "socket %x: Retransmission timer expired\n", eax |
push eax |
call TCP_output |
pop eax |
.check_more2: |
dec [eax + TCP_SOCKET.timer_keepalive] |
jnz .check_more3 |
test [eax + TCP_SOCKET.timer_flags], timer_flag_keepalive |
jz .check_more3 |
DEBUGF DEBUG_NETWORK_VERBOSE, "socket %x: Keepalive expired\n", eax |
cmp [eax + TCP_SOCKET.state], TCPS_ESTABLISHED |
ja .dont_kill |
push eax |
call TCP_disconnect |
pop eax |
jmp .loop |
.dont_kill: |
test [eax + SOCKET.options], SO_KEEPALIVE |
jz .reset_keepalive |
push eax |
mov ebx, eax |
xor cl, cl |
call TCP_respond ; send keepalive |
pop eax |
mov [eax + TCP_SOCKET.timer_keepalive], TCP_time_keep_interval |
jmp .check_more3 |
.reset_keepalive: |
mov [eax + TCP_SOCKET.timer_keepalive], TCP_time_keep_idle |
.check_more3: |
dec [eax + TCP_SOCKET.timer_timed_wait] |
jnz .check_more5 |
test [eax + TCP_SOCKET.timer_flags], timer_flag_2msl |
jz .check_more5 |
DEBUGF DEBUG_NETWORK_VERBOSE, "socket %x: 2MSL timer expired\n", eax |
.check_more5: |
dec [eax + TCP_SOCKET.timer_persist] |
jnz .loop |
test [eax + TCP_SOCKET.timer_flags], timer_flag_persist |
jz .loop |
DEBUGF DEBUG_NETWORK_VERBOSE, "socket %x: persist timer expired\n", eax |
call TCP_set_persist |
mov [eax + TCP_SOCKET.t_force], 1 |
push eax |
call TCP_output |
pop eax |
mov [eax + TCP_SOCKET.t_force], 0 |
jmp .loop |
.exit: |
} |
; eax = socket |
TCP_cancel_timers: |
mov [eax + TCP_SOCKET.timer_flags], 0 |
ret |
/kernel/branches/kolibri-process/network/tcp_usreq.inc |
---|
0,0 → 1,203 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; Part of the TCP/IP network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; Based on the code of 4.4BSD ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;------------------------- |
; |
; TCP_usrclose |
; |
; Move connection to next state, based on process close. |
; |
; IN: eax = socket ptr |
; |
;------------------------- |
align 4 |
TCP_usrclosed: |
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_usrclosed: %x\n", eax |
push ebx |
mov ebx, [eax + TCP_SOCKET.t_state] |
mov ebx, dword [.switch + ebx*4] |
jmp ebx |
.switch: |
dd .close ; TCPS_CLOSED |
dd .close ; TCPS_LISTEN |
dd .close ; TCPS_SYN_SENT |
dd .wait1 ; TCPS_SYN_RECEIVED |
dd .wait1 ; TCPS_ESTABLISHED |
dd .last_ack ; TCPS_CLOSE_WAIT |
dd .ret ; TCPS_FIN_WAIT_1 |
dd .ret ; TCPS_CLOSING |
dd .ret ; TCPS_LAST_ACK |
dd .disc ; TCPS_FIN_WAIT_2 |
dd .disc ; TCPS_TIMED_WAIT |
.close: |
mov [eax + TCP_SOCKET.t_state], TCPS_CLOSED |
call TCP_close |
pop ebx |
ret |
.wait1: |
mov [eax + TCP_SOCKET.t_state], TCPS_FIN_WAIT_1 |
pop ebx |
ret |
.last_ack: |
mov [eax + TCP_SOCKET.t_state], TCPS_LAST_ACK |
pop ebx |
ret |
.disc: |
call SOCKET_is_disconnected |
.ret: |
pop ebx |
ret |
;------------------------- |
; |
; TCP_connect |
; |
; IN: eax = socket ptr |
; OUT: eax = 0 ok / -1 error |
; ebx = error code |
; |
;------------------------- |
align 4 |
TCP_connect: |
test [eax + SOCKET.state], SS_ISCONNECTED |
jnz .eisconn |
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 [IP_LIST + 4] ; FIXME: use correct local IP |
pop [eax + IP_SOCKET.LocalIP] |
; Fill in remote port and IP |
pushw [edx + 2] |
pop [eax + TCP_SOCKET.RemotePort] |
pushd [edx + 4] |
pop [eax + IP_SOCKET.RemoteIP] |
; Find a local port, if user didnt define one |
cmp [eax + TCP_SOCKET.LocalPort], 0 |
jne @f |
call SOCKET_find_port |
@@: |
; Start the TCP sequence |
mov [eax + TCP_SOCKET.timer_persist], 0 |
mov [eax + TCP_SOCKET.t_state], TCPS_SYN_SENT |
push [TCP_sequence_num] |
add [TCP_sequence_num], 6400 |
pop [eax + TCP_SOCKET.ISS] |
mov [eax + TCP_SOCKET.timer_keepalive], TCP_time_keep_init |
TCP_sendseqinit eax |
mov ebx, eax |
lea eax, [ebx + STREAM_SOCKET.snd] |
call SOCKET_ring_create |
test eax, eax |
jz .nomem |
lea eax, [ebx + STREAM_SOCKET.rcv] |
call SOCKET_ring_create |
test eax, eax |
jz .nomem |
push ebx |
lea ecx, [ebx + SOCKET.mutex] |
call mutex_unlock |
pop eax |
call SOCKET_is_connecting |
; Now send the SYN packet to remote end |
push eax |
call TCP_output |
pop eax |
.block: |
test [eax + SOCKET.options], SO_NONBLOCK |
jz .waitforit |
xor eax, eax |
dec eax |
mov ebx, EINPROGRESS |
ret |
.nomem: |
xor eax, eax |
dec eax |
mov ebx, ENOMEM |
ret |
.eisconn: |
xor eax, eax |
dec eax |
mov ebx, EISCONN |
ret |
.waitforit: |
push eax |
stdcall timer_hs, TCP_time_connect, 0, .timeout, eax |
pop ebx |
mov [ebx + TCP_SOCKET.timer_connect], eax |
mov eax, ebx |
.loop: |
cmp [eax + SOCKET.errorcode], 0 |
jne .fail |
cmp [eax + TCP_SOCKET.t_state], TCPS_ESTABLISHED |
je .established |
call SOCKET_block |
jmp .loop |
.timeout: |
mov eax, [esp+4] |
mov [eax + SOCKET.errorcode], ETIMEDOUT |
and [eax + SOCKET.state], not SS_ISCONNECTING |
call SOCKET_notify.unblock |
ret 4 |
.fail: |
mov ebx, [eax + SOCKET.errorcode] |
mov [eax + SOCKET.errorcode], 0 ; Clear the error, we only need to send it to the caller once |
xor eax, eax |
dec eax |
ret |
.established: |
stdcall cancel_timer_hs, [eax + TCP_SOCKET.timer_connect] |
xor eax, eax |
ret |
/kernel/branches/kolibri-process/network/udp.inc |
---|
0,0 → 1,424 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2013. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; UDP.INC ;; |
;; ;; |
;; Part of the tcp/ip network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision: 2995 $ |
struct UDP_header |
SourcePort dw ? |
DestinationPort dw ? |
Length dw ? ; Length of (UDP Header + Data) |
Checksum dw ? |
ends |
uglobal |
align 4 |
UDP_PACKETS_TX rd NET_DEVICES_MAX |
UDP_PACKETS_RX rd NET_DEVICES_MAX |
endg |
;----------------------------------------------------------------- |
; |
; UDP_init |
; |
; This function resets all UDP variables |
; |
;----------------------------------------------------------------- |
macro UDP_init { |
xor eax, eax |
mov edi, UDP_PACKETS_TX |
mov ecx, 2*NET_DEVICES_MAX |
rep stosd |
} |
macro UDP_checksum IP1, IP2 { ; esi = ptr to udp packet, ecx = packet size, destroys: ecx, edx |
; Pseudoheader |
mov edx, IP_PROTO_UDP |
add dl, [IP1+1] |
adc dh, [IP1+0] |
adc dl, [IP1+3] |
adc dh, [IP1+2] |
adc dl, [IP2+1] |
adc dh, [IP2+0] |
adc dl, [IP2+3] |
adc dh, [IP2+2] |
adc dl, cl ; byte[esi+UDP_header.Length+1] |
adc dh, ch ; byte[esi+UDP_header.Length+0] |
; Done with pseudoheader, now do real header |
adc dl, byte[esi+UDP_header.SourcePort+1] |
adc dh, byte[esi+UDP_header.SourcePort+0] |
adc dl, byte[esi+UDP_header.DestinationPort+1] |
adc dh, byte[esi+UDP_header.DestinationPort+0] |
adc dl, byte[esi+UDP_header.Length+1] |
adc dh, byte[esi+UDP_header.Length+0] |
adc edx, 0 |
; Done with header, now do data |
push esi |
movzx ecx, [esi+UDP_header.Length] |
rol cx , 8 |
sub cx , sizeof.UDP_header |
add esi, sizeof.UDP_header |
call checksum_1 |
call checksum_2 |
pop esi |
add [esi+UDP_header.Checksum], dx ; this final instruction will set or clear ZF :) |
} |
;----------------------------------------------------------------- |
; |
; UDP_input: |
; |
; Called by IPv4_input, |
; this procedure will inject the udp data diagrams in the application sockets. |
; |
; IN: [esp] = Pointer to buffer |
; [esp+4] = size of buffer |
; ebx = ptr to device struct |
; ecx = UDP Packet size |
; esi = ptr to UDP header |
; edi = ptr to ipv4 source and dest address |
; |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
UDP_input: |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_input: size=%u\n", ecx |
; First validate, checksum |
neg [esi + UDP_header.Checksum] ; substract checksum from 0 |
jz .no_checksum ; if checksum is zero, it is considered valid |
; otherwise, we will re-calculate the checksum and add it to this value, thus creating 0 when it is correct |
UDP_checksum (edi), (edi+4) |
jnz .checksum_mismatch |
.no_checksum: |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_input: checksum ok\n" |
; Convert length to little endian |
rol [esi + UDP_header.Length], 8 |
; Look for a socket where |
; IP Packet UDP Destination Port = local Port |
; IP Packet SA = Remote IP |
pusha |
mov ecx, socket_mutex |
call mutex_lock |
popa |
mov cx, [esi + UDP_header.SourcePort] |
mov dx, [esi + UDP_header.DestinationPort] |
mov edi, [edi + 4] ; ipv4 source address |
mov eax, net_sockets |
.next_socket: |
mov eax, [eax + SOCKET.NextPtr] |
or eax, eax |
jz .dump_ |
cmp [eax + SOCKET.Domain], AF_INET4 |
jne .next_socket |
cmp [eax + SOCKET.Protocol], IP_PROTO_UDP |
jne .next_socket |
cmp [eax + UDP_SOCKET.LocalPort], dx |
jne .next_socket |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_input: socket=%x\n", eax |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
;;; TODO: when packet is processed, check more sockets! |
; cmp [eax + IP_SOCKET.RemoteIP], 0xffffffff |
; je @f |
; cmp [eax + IP_SOCKET.RemoteIP], edi |
; jne .next_socket |
; @@: |
; |
; FIXME: UDP should check remote IP, but not under all circumstances! |
cmp [eax + UDP_SOCKET.RemotePort], 0 |
je .updateport |
cmp [eax + UDP_SOCKET.RemotePort], cx |
jne .dump |
pusha |
lea ecx, [eax + SOCKET.mutex] |
call mutex_lock |
popa |
.updatesock: |
call NET_ptr_to_num4 |
inc [UDP_PACKETS_RX + edi] |
movzx ecx, [esi + UDP_header.Length] |
sub ecx, sizeof.UDP_header |
add esi, sizeof.UDP_header |
jmp SOCKET_input |
.updateport: |
pusha |
lea ecx, [eax + SOCKET.mutex] |
call mutex_lock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_input: new remote port=%x\n", cx ; FIXME: find a way to print big endian values with debugf |
mov [eax + UDP_SOCKET.RemotePort], cx |
jmp .updatesock |
.dump_: |
pusha |
mov ecx, socket_mutex |
call mutex_unlock |
popa |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_input: no socket found\n" |
jmp .dump |
.checksum_mismatch: |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_input: checksum mismatch\n" |
.dump: |
call NET_packet_free |
add esp, 4 ; pop (balance stack) |
DEBUGF DEBUG_NETWORK_VERBOSE,"UDP_input: dumping\n" |
ret |
;----------------------------------------------------------------- |
; |
; UDP_output |
; |
; IN: eax = socket pointer |
; ecx = number of bytes to send |
; esi = pointer to data |
; |
;----------------------------------------------------------------- |
align 4 |
UDP_output: |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_output: socket=%x bytes=%u data_ptr=%x\n", eax, ecx, esi |
mov dx, [eax + UDP_SOCKET.RemotePort] |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_output: remote port=%x, ", dx ; FIXME: find a way to print big endian values with debugf |
rol edx, 16 |
mov dx, [eax + UDP_SOCKET.LocalPort] |
DEBUGF DEBUG_NETWORK_VERBOSE, "local port=%x\n", dx |
sub esp, 8 ; Data ptr and data size will be placed here |
push edx esi |
mov edx, [eax + IP_SOCKET.LocalIP] |
mov eax, [eax + IP_SOCKET.RemoteIP] |
mov di, IP_PROTO_UDP shl 8 + 128 |
add ecx, sizeof.UDP_header |
call IPv4_output |
jz .fail |
mov [esp + 8], eax ; pointer to buffer start |
mov [esp + 8 + 4], edx ; buffer size |
mov [edi + UDP_header.Length], cx |
rol [edi + UDP_header.Length], 8 |
pop esi |
push edi ecx |
sub ecx, sizeof.UDP_header |
add edi, sizeof.UDP_header |
shr ecx, 2 |
rep movsd |
mov ecx, [esp] |
and ecx, 3 |
rep movsb |
pop ecx edi |
pop dword [edi + UDP_header.SourcePort] |
; Checksum |
mov esi, edi |
mov [edi + UDP_header.Checksum], 0 |
UDP_checksum (edi-4), (edi-8) ; FIXME: IPv4 packet could have options.. |
DEBUGF DEBUG_NETWORK_VERBOSE, "UDP_output: sending with device %x\n", ebx |
call [ebx + NET_DEVICE.transmit] |
test eax, eax |
jnz @f |
call NET_ptr_to_num4 |
inc [UDP_PACKETS_TX + edi] |
@@: |
ret |
.fail: |
DEBUGF DEBUG_NETWORK_ERROR, "UDP_output: failed\n" |
add esp, 4+4+8 |
or eax, -1 |
ret |
;----------------------------------------------------------------- |
; |
; UDP_connect |
; |
; IN: eax = socket pointer |
; OUT: eax = 0 ok / -1 error |
; ebx = error code |
; |
;------------------------- |
align 4 |
UDP_connect: |
test [eax + SOCKET.state], SS_ISCONNECTED |
jz @f |
call UDP_disconnect |
@@: |
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 [IP_LIST + 4] ; FIXME: use correct local IP |
pop [eax + IP_SOCKET.LocalIP] |
; Fill in remote port and IP, overwriting eventually previous values |
pushw [edx + 2] |
pop [eax + UDP_SOCKET.RemotePort] |
pushd [edx + 4] |
pop [eax + IP_SOCKET.RemoteIP] |
; Find a local port, if user didnt define one |
cmp [eax + UDP_SOCKET.LocalPort], 0 |
jne @f |
call SOCKET_find_port |
@@: |
push eax |
init_queue (eax + SOCKET_QUEUE_LOCATION) ; Set up data receiving queue |
pop eax |
push eax |
lea ecx, [eax + SOCKET.mutex] |
call mutex_unlock |
pop eax |
call SOCKET_is_connected |
xor eax, eax |
ret |
;----------------------------------------------------------------- |
; |
; UDP_disconnect |
; |
; IN: eax = socket pointer |
; OUT: eax = socket pointer |
; |
;------------------------- |
align 4 |
UDP_disconnect: |
; TODO: remove the pending received data |
call SOCKET_is_disconnected |
ret |
;--------------------------------------------------------------------------- |
; |
; UDP_API |
; |
; This function is called by system function 75 |
; |
; IN: subfunction number in bl |
; device number in bh |
; ecx, edx, .. depends on subfunction |
; |
; OUT: |
; |
;--------------------------------------------------------------------------- |
align 4 |
UDP_api: |
movzx eax, bh |
shl eax, 2 |
test bl, bl |
jz .packets_tx ; 0 |
dec bl |
jz .packets_rx ; 1 |
.error: |
mov eax, -1 |
ret |
.packets_tx: |
mov eax, [UDP_PACKETS_TX + eax] |
ret |
.packets_rx: |
mov eax, [UDP_PACKETS_RX + eax] |
ret |