1,6 → 1,6 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2009. All rights reserved. ;; |
;; Copyright (C) KolibriOS team 2004-2010. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; TCP.INC ;; |
8,25 → 8,57 |
;; Part of the tcp/ip network stack for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; Inspired by the TCP code of Mike Hibbit for MenuetOS ;; |
;; ;; |
;; Based on the code of 4.4BSD ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
|
$Revision$ |
|
TCP_RETRIES equ 5 ; Number of times to resend a Packet |
TCP_PACKET_TTL equ 50 ; resend if not replied to in 1/100 s |
TCP_SOCKET_TTL equ 10 ; # of secs to wait before closing socket |
TCP_QUEUE_SIZE equ 16 |
; Socket states |
TCB_CLOSED equ 0 |
TCB_LISTEN equ 1 |
TCB_SYN_SENT equ 2 |
TCB_SYN_RECEIVED equ 3 |
TCB_ESTABLISHED equ 4 |
TCB_CLOSE_WAIT equ 5 |
TCB_FIN_WAIT_1 equ 6 |
TCB_CLOSING equ 7 |
TCB_LAST_ACK equ 8 |
TCB_FIN_WAIT_2 equ 9 |
TCB_TIMED_WAIT equ 10 |
|
TCP_MAX_ACKS equ 16 |
; Socket Flags |
TF_ACKNOW equ 1 shl 0 ; ack peer immediately |
TF_DELACK equ 1 shl 1 ; ack, but try to delay it |
TF_NODELAY equ 1 shl 2 ; don't delay packets to coalesce |
TF_NOOPT equ 1 shl 3 ; don't use tcp options |
TF_SENTFIN equ 1 shl 4 ; have sent FIN |
TF_REQ_SCALE equ 1 shl 5 ; have/will request window scaling |
TF_RCVD_SCALE equ 1 shl 6 ; other side has requested scaling |
TF_REQ_TSTMP equ 1 shl 7 ; have/will request timestamps |
TF_RCVD_TSTMP equ 1 shl 8 ; a timestamp was received in SYN |
TF_SACK_PERMIT equ 1 shl 9 ; other side said I could SACK |
|
; Segment flags |
TH_FIN equ 1 shl 0 |
TH_SYN equ 1 shl 1 |
TH_RST equ 1 shl 2 |
TH_PUSH equ 1 shl 3 |
TH_ACK equ 1 shl 4 |
TH_URG equ 1 shl 5 |
|
struct TCP_Packet |
; Segment header options |
TCP_OPT_EOL equ 0 ; End of option list. |
TCP_OPT_NOP equ 1 ; No-Operation. |
TCP_OPT_MAXSEG equ 2 ; Maximum Segment Size. |
TCP_OPT_WINDOW equ 3 ; window scale |
TCP_OPT_TIMESTAMP equ 8 |
|
struct TCP_segment |
.SourcePort dw ? |
.DestinationPort dw ? |
.SequenceNumber dd ? |
36,15 → 68,13 |
.Window dw ? |
.Checksum dw ? |
.UrgentPointer dw ? |
; .Options rb 3 |
; .Padding db ? |
.Data: |
.Data: ; ..or options |
ends |
|
struct tcp_in_queue_entry |
.data_ptr dd ? |
.data_size dd ? |
.offset dd ? ; TODO: replace this in code by absolute address isntead of relative offset |
.offset dd ? |
.size: |
ends |
|
51,47 → 81,20 |
struct tcp_out_queue_entry |
.data_ptr dd ? |
.data_size dd ? |
.ttl dd ? |
.retries dd ? |
.owner dd ? |
.sendproc dd ? |
.seq_num dd ? |
.socket dd ? |
|
.size: |
ends |
|
align 4 |
uglobal |
TCP_PACKETS_TX rd MAX_IP |
TCP_PACKETS_RX rd MAX_IP |
|
TCP_IN_QUEUE rd (tcp_in_queue_entry.size*TCP_QUEUE_SIZE+queue.data)/4 |
TCP_OUT_QUEUE dd ?, ? |
rd (tcp_out_queue_entry.size*TCP_QUEUE_SIZE)/4 |
|
TCP_ACKS dd ? |
TCP_ACK_LIST rd 3*TCP_MAX_ACKS |
TCP_segments_tx rd IP_MAX_INTERFACES |
TCP_segments_rx rd IP_MAX_INTERFACES |
TCP_bytes_rx rq IP_MAX_INTERFACES |
TCP_bytes_tx rq IP_MAX_INTERFACES |
TCP_sequence_num dd ? |
endg |
|
align 4 |
iglobal |
TCPstateHandler: |
|
dd stateTCB_LISTEN |
dd stateTCB_SYN_SENT |
dd stateTCB_SYN_RECEIVED |
dd stateTCB_ESTABLISHED |
dd stateTCB_FIN_WAIT_1 |
dd stateTCB_FIN_WAIT_2 |
dd stateTCB_CLOSE_WAIT |
dd stateTCB_CLOSING |
dd stateTCB_LAST_ACK |
dd stateTCB_TIME_WAIT |
dd stateTCB_CLOSED |
|
endg |
|
|
;----------------------------------------------------------------- |
; |
; TCP_init |
106,73 → 109,60 |
TCP_init: |
|
xor eax, eax |
mov edi, TCP_PACKETS_TX |
mov ecx, 2*MAX_IP |
mov edi, TCP_segments_tx |
mov ecx, (6*IP_MAX_INTERFACES) |
rep stosd |
|
init_queue TCP_IN_QUEUE |
mov [TCP_sequence_num],1 |
|
; tcp_out_queue is a special type of queue: |
; The first dword is a counter of total packets queued. |
; The remaining bytes are socket 'slots' wich use tcp_out_queue_entry data structure. |
; An empty slot is know by the fact that tcp_out_queue_entry.data_ptr (first dword of the slot) is set to 0 |
; There are TCP_OUT_QUEUE_SIZE number of slots |
|
xor eax, eax |
mov esi, TCP_OUT_QUEUE |
mov ecx, TCP_QUEUE_SIZE*tcp_out_queue_entry/4+2+2+3*TCP_MAX_ACKS |
rep stosd |
|
ret |
|
|
;----------------------------------------------------------------- |
; |
; TCP_decrease_socket_ttls |
; decrease socket ttls |
; |
; IN: / |
; OUT: / |
; |
; destroys: eax |
; |
;----------------------------------------------------------------- |
align 4 |
TCP_decrease_socket_ttls: |
; scan through all the sockets, decrementing active timers |
TCP_timer_1000ms: |
; scan through all the active TCP sockets, decrementing active timers |
|
mov ebx, net_sockets |
cmp [ebx + SOCKET_head.NextPtr], 0 |
je .exit |
.next_socket: |
mov ebx, [ebx + SOCKET_head.NextPtr] |
or ebx, ebx |
mov eax, net_sockets |
.loop: |
mov eax, [eax + SOCKET.NextPtr] |
.check_only: |
or eax, eax |
jz .exit |
|
cmp [ebx + SOCKET_head.Type], IP_PROTO_TCP |
jne .next_socket |
cmp [eax + SOCKET.Type], IP_PROTO_TCP |
jne .loop |
|
; DEBUGF 1, "K : %x-%x: %x-%x-%x-%u\n", [ebx + SOCKET.PID]:2, [ebx + SOCKET.Number]:2, [ebx + SOCKET.LocalPort]:4, [ebx + SOCKET.RemoteIP], [ebx + SOCKET.RemotePort]:4, [ebx + SOCKET.state] |
|
cmp [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.timer], 0 |
cmp [eax + TCP_SOCKET.t_timer], 0 |
jne .decrement_tcb |
cmp [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.wndsizeTimer], 0 |
;;;;;; cmp [eax + TCP_SOCKET.wndsizeTimer], 0 |
jne .decrement_wnd |
jmp .next_socket |
jmp .loop |
|
.decrement_tcb: |
; decrement it, delete socket if TCB timer = 0 & socket in timewait state |
dec [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.timer] |
jnz .next_socket |
dec [eax + TCP_SOCKET.t_timer] |
jnz .loop |
|
cmp [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_TIMED_WAIT |
jne .next_socket |
cmp [eax + TCP_SOCKET.t_state], TCB_TIMED_WAIT |
jne .loop |
|
push [ebx + SOCKET_head.PrevPtr] |
stdcall net_socket_free, ebx |
pop ebx |
jmp .next_socket |
push [eax + SOCKET.NextPtr] |
call SOCKET_free |
pop eax |
jmp .check_only |
|
.decrement_wnd: |
dec [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.wndsizeTimer] |
jmp .next_socket |
;;;;;; dec [eax + TCP_SOCKET.wndsizeTimer] |
|
.exit: |
ret |
179,1067 → 169,1815 |
|
|
|
;----------------------------------------------------------------- |
;---------------------- |
; |
; TCP_send_queued: |
; TCP_500ms |
; |
; Decreases 'ttl' of tcp packets queued. |
; if 'ttl' reaches 0, resend the packet and decrease 'retries' |
; if 'retries' reaches zero, remove the queued packet |
; |
; IN: / |
; OUT: / |
; |
;----------------------------------------------------------------- |
;---------------------- |
align 4 |
TCP_send_queued: |
TCP_500ms: |
|
cmp [TCP_OUT_QUEUE], 0 |
je .exit |
add [TCP_sequence_num], 64000 |
|
mov ebx, TCP_OUT_QUEUE+4 |
call wait_mutex |
|
mov eax, TCP_QUEUE_SIZE |
mov ecx, [TCP_OUT_QUEUE] |
mov esi, TCP_OUT_QUEUE+8 |
|
.loop: |
cmp [esi + tcp_out_queue_entry.data_ptr], 0 |
jnz .found_one |
add esi, tcp_out_queue_entry.size |
loop .loop |
.exit: |
mov [TCP_OUT_QUEUE+4], 0 |
ret |
|
.found_one: |
dec [esi + tcp_out_queue_entry.ttl] |
jz .send_it |
cmp [esi + tcp_out_queue_entry.data_ptr], -1 |
jz .is_ack |
.find_next: |
add esi, tcp_out_queue_entry.size |
dec eax |
jz .exit |
test ecx, ecx |
jnz .loop |
mov [TCP_OUT_QUEUE+4], 0 |
ret |
|
.send_it: |
pusha |
mov ebx, [esi + tcp_out_queue_entry.owner] |
pushd [esi + tcp_out_queue_entry.data_size] |
pushd [esi + tcp_out_queue_entry.data_ptr] |
DEBUGF 1,"Now sending TCP packet %x, size: %u, owner: %x, sendproc %x\n", [esp], [esp+4], ebx, [esi + tcp_out_queue_entry.sendproc] |
inc [TCP_PACKETS_TX] |
call [esi + tcp_out_queue_entry.sendproc] |
add esp, 8 |
popa |
|
dec [esi + tcp_out_queue_entry.retries] |
jz .remove_it |
;---------------------- |
; |
; TCP_10ms |
; |
;---------------------- |
align 4 |
TCP_10ms: |
|
mov [esi + tcp_out_queue_entry.ttl], TCP_PACKET_TTL |
jmp .find_next |
; todo: decrease timers |
|
.remove_it: |
push [esi + tcp_out_queue_entry.data_ptr] |
mov [esi + tcp_out_queue_entry.data_ptr], 0 |
call kernel_free |
dec [TCP_OUT_QUEUE] |
jmp .find_next |
ret |
|
.is_ack: |
pusha |
mov eax, [esi + tcp_out_queue_entry.socket] |
mov ebx, [esi + tcp_out_queue_entry.owner] |
mov ecx, [esi + tcp_out_queue_entry.size] |
call TCP_send_ack |
popa |
mov [esi + tcp_out_queue_entry.data_ptr], 0 |
dec [TCP_OUT_QUEUE] |
jmp .find_next |
|
|
|
;----------------------------------------------------------------- |
; |
; TCP_handler: |
; TCP_input: |
; |
; Called by IPv4_handler, |
; this procedure will inject the tcp data diagrams in the application sockets. |
; IN: [esp] = ptr to buffer |
; [esp+4] = buffer size |
; ebx = ptr to device struct |
; ecx = segment size |
; edx = ptr to TCP segment |
; |
; IN: Pointer to buffer in [esp] |
; size of buffer in [esp+4] |
; pointer to device struct in ebx |
; TCP Packet size in ecx |
; pointer to TCP Packet in edx |
; SourceAddres (IPv4) in esi |
; esi = ipv4 source address |
; edi = ipv4 dest address |
; |
; OUT: / |
; |
;----------------------------------------------------------------- |
align 4 |
TCP_handler : |
TCP_input: |
|
DEBUGF 1,"TCP_Handler\n" |
DEBUGF 1,"TCP_input\n" |
|
; TODO: validate checksum |
; Offset must be greater than or equal to the size of the standard TCP header (20) and less than or equal to the TCP length. |
|
; Find a matching socket for received packet, all following expressions must be valid: |
; |
movzx eax, [edx + TCP_segment.DataOffset] |
and eax, 0xf0 |
shr al , 2 |
|
DEBUGF 1,"data offset: %u\n", eax |
|
cmp eax, 20 |
jl .drop |
|
cmp eax, ecx |
jg .drop |
|
;------------------------------- |
; Now, re-calculate the checksum |
|
push eax edx ebx |
|
push edi |
push esi |
mov esi, edx |
call TCP_checksum ; this destroys edx, ecx and esi (but not edi! :) |
|
pop ebx edx eax |
|
cmp [edx + TCP_segment.Checksum], 0 |
jnz .drop |
|
DEBUGF 1,"Checksum is correct\n" |
|
;----------------------------------------------------------------------------------------- |
; Check if this packet has a timestamp option (We do it here so we can process it quickly) |
|
cmp eax, 20 + 12 ; Timestamp option is 12 bytes |
jl .no_timestamp |
je .is_ok |
|
cmp byte [edx + TCP_segment.Data + 12], 0 ; end of option list |
jne .no_timestamp |
|
.is_ok: |
test [edx + TCP_segment.Flags], TH_SYN ; SYN flag must not be set |
jnz .no_timestamp |
|
cmp dword [edx + TCP_segment.Data], 0x0101080a ; Timestamp header |
jne .no_timestamp |
|
DEBUGF 1,"timestamp ok\n" |
|
; TODO: Parse the options |
; TODO: Set a Bit in the TCP to tell all options are parsed |
|
ret |
|
.no_timestamp: |
|
;------------------------------------------- |
; Convert Big-endian values to little endian |
|
ntohld [edx + TCP_segment.SequenceNumber] |
ntohld [edx + TCP_segment.AckNumber] |
|
ntohlw [edx + TCP_segment.Window] |
ntohlw [edx + TCP_segment.UrgentPointer] |
|
;------------------------------------------------------------ |
; Next thing to do is find the TCB (thus, the socket pointer) |
|
; IP Packet TCP Destination Port = local Port |
; (IP Packet SA = Remote IP) OR (Remote IP = 0) |
; (IP Packet SenderAddress = Remote IP) OR (Remote IP = 0) |
; (IP Packet TCP Source Port = remote Port) OR (remote Port = 0) |
|
mov ebx, net_sockets |
|
.socket_loop: |
mov ebx, [ebx + SOCKET_head.NextPtr] |
mov ebx, [ebx + SOCKET.NextPtr] |
or ebx, ebx |
jz .dump |
jz .drop_with_reset |
|
mov ax, [edx + TCP_Packet.DestinationPort] |
cmp [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.LocalPort], ax |
cmp [ebx + SOCKET.Type], IP_PROTO_TCP |
jne .socket_loop |
|
mov eax, [ebx + SOCKET_head.end + IPv4_SOCKET.RemoteIP] |
mov ax, [edx + TCP_segment.DestinationPort] |
cmp [ebx + TCP_SOCKET.LocalPort], ax |
jne .socket_loop |
|
mov eax, [ebx + IP_SOCKET.RemoteIP] |
cmp eax, esi |
je @f |
test eax, eax |
jne .socket_loop |
jnz .socket_loop |
@@: |
|
mov ax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RemotePort] |
cmp [edx + TCP_Packet.SourcePort] , ax |
mov ax, [ebx + TCP_SOCKET.RemotePort] |
cmp [edx + TCP_segment.SourcePort] , ax |
je .found_socket |
test ax, ax |
jnz .socket_loop |
.found_socket: |
DEBUGF 1,"Found valid socket for packet\n" |
DEBUGF 1,"Socket ptr: %x\n", ebx |
|
inc [TCP_PACKETS_RX] |
; ebx now contains the pointer to the socket |
|
add ebx, SOCKET_head.lock |
;---------------------------- |
; Check if socket isnt closed |
|
cmp [TCP_SOCKET.t_state], TCB_CLOSED |
je .drop |
|
;---------------- |
; Lock the socket |
|
add ebx, SOCKET.lock ; TODO: figure out if we should lock now already |
call wait_mutex |
sub ebx, SOCKET_head.lock |
sub ebx, SOCKET.lock |
|
;------------------------------- |
; ebx is pointer to socket |
; ecx is size of tcp packet |
; edx is pointer to tcp packet |
;--------------------------------------- |
; unscale the window into a 32 bit value ;;;;;; |
|
; calculate header length |
movzx eax, [edx + TCP_Packet.DataOffset] |
movzx eax, [edx + TCP_segment.Window] |
xchg al, ah |
|
test [edx + TCP_segment.Flags], TH_SYN |
jnz .no_syn |
|
mov cl , [ebx + TCP_SOCKET.SND_SCALE] |
shl eax, cl |
|
.no_syn: |
|
;----------------------------------- |
; Is this socket a listening socket? |
|
; If so, create a new socket |
|
test [ebx + SOCKET.options], SO_ACCEPTCON |
jz .no_accept_conn |
|
|
; TODO: create a new socket |
|
|
.no_accept_conn: |
|
;---------------------------- |
; Compute window scale factor |
|
|
; TODO |
|
|
;------------------------------------- |
; Reset idle timer and keepalive timer |
|
|
; TODO |
|
;----------------------------------------- |
; Process TCP options if not in LISTEN state |
|
test [ebx + TCP_SOCKET.t_state], TCB_LISTEN |
jz .dont_do_options |
|
call TCP_do_options |
|
.dont_do_options: |
|
;----------------------------------------------------------------------- |
; 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 [TCP_SOCKET.t_state], TCB_ESTABLISHED |
jnz .not_uni_xfer |
|
test [TCP_segment.Flags], TH_SYN + TH_FIN + TH_RST + TH_URG |
jnz .not_uni_xfer |
|
test [TCP_segment.Flags], TH_ACK |
jz .not_uni_xfer |
|
mov eax, [edx + TCP_segment.SequenceNumber] |
cmp eax, [ebx + TCP_SOCKET.RCV_NXT] |
jne .not_uni_xfer |
|
movzx eax, [edx + TCP_segment.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 |
|
;------------------------------------------------------------------------------- |
; If last ACK falls within this segment's sequence number, record the timestamp. |
|
; TODO: check if it has a timestamp |
|
|
|
|
;--------------------------------------- |
; 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 (ti_len is 0). |
|
movzx eax, [edx + TCP_segment.DataOffset] |
and eax, 11110000b |
shr eax, 2 |
DEBUGF 1,"TCP header size: %u\n", eax |
sub ecx, eax |
jnz .not_sender |
|
;------------------------------- |
; ecx is size of tcp data |
; - The acknowledgment field in the segment (ti_ack) is greater than the largest unacknowledged sequence number (snd_una). |
; Since this test is "greater than" and not "greater than or equal to," it is true only if some positive amount of data is acknowledged by the ACK. |
|
; as a Packet has been received, update the TCB timer |
mov eax, [edx + TCP_segment.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.SND_UNA] |
jle .not_uni_xfer |
|
; If the received Packet has an ACK bit set, remove any Packets in the resend queue that this received Packet acknowledges |
test [edx + TCP_Packet.Flags], TH_ACK |
jz .no_ack ; No ACK, so no data yet |
; - The acknowledgment field in the segment (ti_ack) is less than or equal to the maximum sequence number sent (snd_max). |
|
; Calculate ACK number, in intel byte order |
mov edi, [edx + TCP_Packet.AckNumber] |
bswap edi |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.last_ack_number], edi |
DEBUGF 1,"Setting last_ack_number to %u\n", edi |
; mov eax, [edx + TCP_segment.Ack] |
cmp eax, [ebx + TCP_SOCKET.SND_MAX] |
jg .not_uni_xfer |
|
; Dequeue all acknowledged packets |
cmp [TCP_OUT_QUEUE], 0 ; first, check if any packets are queued at all |
je .no_ack |
; - The congestion window (snd_cwnd) is greater than or equal to the current send window (snd_wnd). |
; 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. |
|
push ebx |
mov ebx, TCP_OUT_QUEUE+4 |
call wait_mutex |
pop ebx |
mov eax, [ebx + TCP_SOCKET.SND_CWND] |
cmp eax, [ebx + TCP_SOCKET.SND_WND] |
jl .not_uni_xfer |
|
push ecx |
DEBUGF 1,"Removing all queued packets with smaller ACK\n" |
mov ecx, TCP_QUEUE_SIZE |
mov esi, TCP_OUT_QUEUE+8 |
.loop: |
cmp [esi + tcp_out_queue_entry.data_ptr], 0 |
je .maybe_next |
DEBUGF 1,"Header prediction: we are sender\n" |
|
cmp [esi + tcp_out_queue_entry.socket], ebx |
jne .maybe_next |
;--------------------------------- |
; Packet is a pure ACK, process it |
|
cmp [esi + tcp_out_queue_entry.seq_num], edi |
jg .maybe_next |
; Update RTT estimators |
|
DEBUGF 1,"Removing a queued packet\n" |
; Delete acknowledged bytes from send buffer |
|
push [esi + tcp_out_queue_entry.data_ptr] |
mov [esi + tcp_out_queue_entry.data_ptr], 0 |
dec [TCP_OUT_QUEUE] |
call kernel_free |
; Stop retransmit timer |
|
.maybe_next: |
add esi, tcp_out_queue_entry.size |
loop .loop |
; Awaken waiting processes |
|
mov [TCP_OUT_QUEUE+4], 0 |
pop ecx |
; Generate more output |
|
; Now call the correct handler, depending on the socket state |
.no_ack: |
mov eax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state] |
|
cmp eax, TCB_LISTEN |
jb .dump |
cmp eax, TCB_CLOSED |
ja .dump |
|
call dword [TCPstateHandler+eax*4-4] |
|
.dump: |
DEBUGF 1,"Dumping TCP packet\n" |
call kernel_free |
add esp, 4 ; pop (balance stack) |
jmp .drop |
|
ret |
|
|
|
;----------------------------------------------------------------- |
; |
; TCP_send (Assumes socket mutex set) |
; |
; IN: eax = socket pointer |
; bl = flags |
; ecx = number of bytes to send, may be set to 0 (single ACK) |
; esi = pointer to data |
; |
;----------------------------------------------------------------- |
;------------------------------------------------- |
; maybe we are the receiver in the uni-xfer then.. |
|
.not_sender: |
; The amount of data in the segment (ti_len) is greater than 0 (data count is in ecx) |
|
|
; The acknowledgment field (ti_ack) equals the largest unacknowledged sequence number. This means no data is acknowledged by this segment. |
mov eax, [edx + TCP_segment.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). |
;;;; |
jnz .not_uni_xfer |
|
; There is room in the receive buffer for the data in the segment. |
;;;; |
jnz .not_uni_xfer |
|
;------------------------------------- |
; Complete processing of received data |
|
DEBUGF 1,"header prediction: we are receiver\nreceiving %u bytes of data\n", ecx |
|
; The next expected receive sequence number (rcv_nxt) is incremented by the number of bytes of data. |
|
add [ebx + TCP_SOCKET.RCV_NXT], ecx |
|
; Add the data to the socket buffer |
|
; The receiving process is awakened (by sorwakeup). |
|
; The delayed-ACK flag is set and the input processing is complete. |
|
jmp .drop |
|
|
|
|
|
;---------------------------------------------------- |
; Header prediction failed, doing it the slow way.. |
|
.not_uni_xfer: |
|
DEBUGF 1,"Header prediction failed\n" |
|
;------------------------ |
; calculate header length ;;;;; we already calculated this before! |
movzx eax, [edx + TCP_segment.DataOffset] |
and eax, 0xf0 |
shr eax, 2 |
|
; Update edx to point to data.. |
add edx, eax |
; ..and ecx to give data size |
sub ecx, eax |
|
;------------------------------ |
; Calculate receive window size |
|
;;;; |
|
|
;------------------------- |
; TCP slow input procedure |
|
DEBUGF 1,"TCP slow input procedure\n" |
|
cmp [eax + TCP_SOCKET.t_state], TCB_LISTEN |
je .LISTEN |
|
cmp [eax + TCP_SOCKET.t_state], TCB_SYN_SENT |
je .SYN_SENT |
|
|
;-------------------------------------------- |
; Protection Against Wrapped Sequence Numbers |
|
|
; First, check timestamp if present |
|
;;;; TODO |
|
; Then, check if at least some bytes of data are within window |
|
;;;; TODO |
|
jmp .trim_then_step6 |
|
align 4 |
TCP_send: |
.LISTEN: |
|
DEBUGF 1,"Creating TCP packet, socket: %x, flags: %x\n",eax, bl |
DEBUGF 1,"TCP state: listen\n" |
|
mov di , IP_PROTO_TCP |
add ecx, TCP_Packet.Data |
test [edx + TCP_segment.Flags], TH_RST |
jnz .drop |
|
push ecx bx eax esi |
; Create an IPv4 Packet of the correct size |
mov ebx, [eax + SOCKET_head.end + IPv4_SOCKET.LocalIP] |
mov eax, [eax + SOCKET_head.end + IPv4_SOCKET.RemoteIP] |
test [edx + TCP_segment.Flags], TH_ACK |
jnz .drop_with_reset |
|
call IPv4_create_packet |
cmp edi, -1 |
je .fail |
test [edx + TCP_segment.Flags], TH_SYN |
jz .drop |
|
; If there is any data, copy it first |
pop esi |
push edi |
add edi, TCP_Packet.Data |
sub ecx, TCP_Packet.Data |
; TODO: check if it's a broadcast or multicast, and drop if so |
|
shr ecx, 1 |
jnc .nb |
movsb |
.nb: shr ecx, 1 |
jnc .nw |
movsw |
.nw: test ecx, ecx |
jz .nd |
;;; 28.6 |
|
; create a new socket and fill in the nescessary variables |
|
;; Exit if backlog queue is full |
; mov ax, [ebx + TCP_SOCKET.backlog_cur] |
; cmp ax, [ebx + TCP_SOCKET.backlog] |
; jae .exit |
|
; Allocate new socket |
call SOCKET_alloc |
;;; jz .fail |
|
; Copy structure from current socket to new, (including lock!) |
; We start at PID to reserve the socket num, and the 2 pointers at beginning of socket |
lea esi, [edx + SOCKET.PID] |
lea edi, [eax + SOCKET.PID] |
mov ecx, (TCP_SOCKET.end - SOCKET.PID + 3)/4 |
rep movsd |
.nd: |
pop edi |
|
; Fill in the TCP header |
pop esi |
;; Push pointer to new socket to queue |
; movzx ecx, [ebx + TCP_SOCKET.backlog_cur] |
; inc [ebx + TCP_SOCKET.backlog_cur] |
; mov [ebx + TCP_SOCKET.end + ecx*4], eax |
|
; fill in tcp sequence number |
push [esi + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT] |
pop [edi + TCP_Packet.SequenceNumber] |
mov [eax + IP_SOCKET.RemoteIP], esi ; IP source address |
|
; Fill in local and remote ports |
push dword [esi + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.LocalPort] |
pop dword [edi + TCP_Packet.SourcePort] |
mov cx, [edx + TCP_segment.SourcePort] |
mov [eax + TCP_SOCKET.RemotePort], cx |
|
; Acknumber |
push [esi + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT] |
pop [edi + TCP_Packet.AckNumber] |
mov ecx, [edx + TCP_segment.SequenceNumber] |
mov [eax + TCP_SOCKET.IRS], ecx |
|
; Fill in other tcp options |
pop cx |
mov [edi + TCP_Packet.Flags], cl |
mov [edi + TCP_Packet.Window], 0x0005 ; 1280 bytes |
mov [edi + TCP_Packet.UrgentPointer], 0 |
mov [edi + TCP_Packet.DataOffset], 0x50 |
mov [edi + TCP_Packet.Checksum], 0 |
mov ecx, [eax + TCP_SOCKET.ISS] |
mov [eax + TCP_SOCKET.SND_NXT], ecx |
|
; Get size of total packet back in ecx |
pop ecx |
; Push pointer to and size of total packet (needed for send procedure) |
push edx eax |
; push socket number (for TCP_add_to_queue) |
push esi |
jmp .trim_then_step6 |
|
; Now, calculate the checksum |
xchg cl, ch |
pushw cx |
xchg cl, ch |
pushw IP_PROTO_TCP shl 8 |
pushd [edi-4] ; destination address ; TODO: fix this, IPv4 packet could have options.. |
pushd [edi-8] ; source address |
|
xor edx, edx |
mov esi, edi |
call checksum_1 |
mov ecx, 12 |
mov esi, esp |
call checksum_1 |
; and store it in TCP header |
call checksum_2 |
mov [edi + TCP_Packet.Checksum], dx |
add esp, 10 ; remove the pseudoheader from stack |
|
DEBUGF 1,"Sending TCP Packet to device %x\n", ebx |
mov edx, [edi + TCP_Packet.SequenceNumber] |
bswap edx |
mov esi, [ebx + ETH_DEVICE.transmit] |
align 4 |
.SYN_SENT: |
|
pop cx ; get the length from packet, back from pseudoheader |
pop edi |
DEBUGF 1,"TCP state: syn_sent\n" |
|
cmp cx, TCP_Packet.Data shl 8 ; if the packet has no data |
je .only_one ; send it only once |
test [edx + TCP_segment.Flags], TH_ACK |
jz @f |
|
and ecx, 0x0000ffff |
xchg cl, ch |
sub cx, TCP_Packet.Data |
mov eax, [edx + TCP_segment.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.ISS] |
jle .drop_with_reset |
|
add_INET (edi + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT) ; todo: this should only happen when packet was queued successful |
mov ecx, TCP_RETRIES |
mov eax, [edx + TCP_segment.AckNumber] |
cmp eax, [ebx + TCP_SOCKET.SND_MAX] |
jg .drop_with_reset |
@@: |
|
jmp .go_for_it |
|
.only_one: |
mov ecx, 1 |
.go_for_it: |
test [edx + TCP_segment.Flags], TH_RST |
jz @f |
|
mov [edi + SOCKET_head.lock], 0 |
jmp TCP_queue ; At last send the packet! |
test [edx + TCP_segment.Flags], TH_ACK |
jz .drop |
|
.fail: |
add esp, 2+4 |
or eax, -1 |
ret |
;tp = tcp_drop(tp, ECONNREFUSED) |
|
jmp .drop |
@@: |
|
;----------------------------------------------------------------- |
; |
; Queue a TCP packet for sending |
; |
; IN: [esp] pointer to buffer |
; [esp + 4] size of buffer |
; ebx = driver struct |
; edx = sequence number of this packet in intel byte order |
; esi = sender proc |
; edi = socket number |
test [edx + TCP_segment.Flags], TH_SYN |
jz .drop |
|
; OUT: / |
; |
;----------------------------------------------------------------- |
; now, process received SYN in response to an active open |
test [edx + TCP_segment.Flags], TH_ACK |
jz @f |
|
mov eax, [edx + TCP_segment.AckNumber] |
mov [ebx + TCP_SOCKET.SND_UNA], eax |
|
mov eax, [ebx + TCP_SOCKET.SND_UNA] |
cmp eax, [ebx + TCP_SOCKET.SND_NXT] |
jle @f |
mov [ebx + TCP_SOCKET.SND_NXT], eax |
|
|
; TODO: turn off retransmission timer |
|
mov eax, [edx + TCP_segment.SequenceNumber] |
mov [ebx + TCP_SOCKET.IRS], eax |
|
; TODO: set socket state to connected |
|
mov [ebx + TCP_SOCKET.t_state], TCB_ESTABLISHED |
|
; TODO: check if we should scale the connection (567-572) |
; TODO: update RTT estimators |
|
|
@@: |
|
; We have received a syn but no ACK, so we are having a simultaneous open.. |
mov [ebx + TCP_SOCKET.t_state], TCB_SYN_RECEIVED |
|
;------------------------------------- |
; Common processing for receipt of SYN |
|
.trimthenstep6: |
|
inc [edx + TCP_segment.SequenceNumber] |
|
cmp cx, [ebx + TCP_SOCKET.RCV_WND] |
jle @f |
|
movzx eax, cx |
sub ax, [ebx + TCP_SOCKET.RCV_WND] |
; TODO: 592 |
mov cx, [ebx + TCP_SOCKET.RCV_WND] |
; TODO... |
@@: |
;;;;; |
;;; jmp .step6 |
|
|
|
|
|
align 4 |
TCP_queue: |
.trim_then_step6: |
|
DEBUGF 1,"Adding packet to TCP queue, buffer: %x, size: %u, driver: %x, acknum: %u\n", [esp], [esp+4], ebx, edx |
DEBUGF 1,"Trim, then step 6\n" |
|
cmp [TCP_OUT_QUEUE], TCP_QUEUE_SIZE |
jge .full |
;---------------------------- |
; trim any data not in window |
|
push ebx |
mov ebx, TCP_OUT_QUEUE+4 |
call wait_mutex |
pop ebx |
mov eax, [ebx + TCP_SOCKET.RCV_NXT] |
sub eax, [edx + TCP_segment.SequenceNumber] |
|
mov ecx, TCP_QUEUE_SIZE |
mov eax, TCP_OUT_QUEUE+8 |
.loop: |
cmp [eax + tcp_out_queue_entry.data_ptr], 0 |
je .found_it |
add eax, tcp_out_queue_entry.size |
loop .loop |
test eax, eax |
jz .no_drop |
|
add esp, 4 |
.full: ; silently discard the packet |
DEBUGF 1,"TCP queue is full!\n" |
test [edx + TCP_segment.Flags], TH_SYN |
jz .no_drop |
|
and [edx + TCP_segment.Flags], not (TH_SYN) |
inc [edx + TCP_segment.SequenceNumber] |
|
cmp [edx + TCP_segment.UrgentPointer], 1 |
jl @f |
|
dec [edx + TCP_segment.UrgentPointer] |
|
jmp .no_drop |
@@: |
|
and [edx + TCP_segment.Flags], not (TH_URG) |
dec eax |
|
.no_drop: |
|
; eax holds number of bytes to drop |
|
|
;---------------------------------- |
; Check for entire duplicate packet |
|
cmp eax, ecx |
jge .duplicate |
|
;;; TODO: figure 28.30 |
|
;; inc [TCP_segments_rx] |
|
;; add dword [TCP_bytes_rx], ecx |
;; adc dword [TCP_bytes_rx+4], 0 |
|
;------------------------ |
; Check for duplicate FIN |
|
test [edx + TCP_segment.Flags], TH_FIN |
jz @f |
inc ecx |
cmp eax, ecx |
dec ecx |
jne @f |
|
mov eax, ecx |
and [edx + TCP_segment.Flags], not TH_FIN |
;;; TODO: set ACKNOW flag |
|
jmp .no_duplicate |
@@: |
|
; Handle the case when a bound socket connects to itself |
; Allow packets with a SYN and an ACKto continue with the processing |
|
|
;------------------------------------- |
; Generate duplicate ACK if nescessary |
|
; This code also handles simultaneous half-open or self-connects |
|
test eax, eax |
jnz .drop_after_ack |
|
cmp [edx + TCP_segment.Flags], TH_ACK |
jz .drop_after_ack |
|
.duplicate: |
|
;---------------------------------------- |
; Update statistics for duplicate packets |
|
;;; TODO |
|
;;; DROP the packet ?? |
|
.no_duplicate: |
|
;----------------------------------------------- |
; Remove duplicate data and update urgent offset |
|
add [edx + TCP_segment.SequenceNumber], eax |
|
;;; TODO |
|
sub [edx + TCP_segment.UrgentPointer], ax |
jg @f |
|
and [edx + TCP_segment.Flags], not (TH_URG) |
mov [edx + TCP_segment.UrgentPointer], 0 |
@@: |
|
;-------------------------------------------------- |
; Handle data that arrives after process terminates |
|
cmp [ebx + SOCKET.PID], 0 |
jge @f |
|
cmp [ebx + TCP_SOCKET.t_state], TCB_CLOSE_WAIT |
jle @f |
|
test ecx, ecx |
jz @f |
|
;;; Close the socket |
;;; update stats |
|
jmp .drop_with_reset |
|
@@: |
|
;---------------------------------------- |
; Remove data beyond right edge of window |
|
mov eax, [edx + TCP_segment.SequenceNumber] |
add eax, ecx |
sub eax, [ebx + TCP_SOCKET.RCV_NXT] |
sub ax, [ebx + TCP_SOCKET.RCV_WND] |
|
; eax now holds the number of bytes to drop |
|
jle .no_excess_data |
|
;;; TODO: update stats |
|
cmp eax, ecx |
jl .dont_drop_all |
|
;;; TODO 700-736 |
|
.dont_drop_all: |
|
.no_excess_data: |
|
|
;----------------- |
; Record timestamp |
|
;;; TODO 737-746 |
|
;------------------ |
; Process RST flags |
|
test [edx + TCP_segment.Flags], TH_RST |
jz .rst_skip |
|
mov eax, [ebx + TCP_SOCKET.t_state] |
shl eax, 2 |
jmp dword [eax + .rst_sw_list] |
|
.rst_sw_list: |
dd .rst_skip ;TCB_CLOSED |
dd .rst_skip ;TCB_LISTEN |
dd .rst_skip ;TCB_SYN_SENT |
dd .econnrefused ;TCB_SYN_RECEIVED |
dd .econnreset ;TCB_ESTABLISHED |
dd .econnreset ;TCB_CLOSE_WAIT |
dd .econnreset ;TCB_FIN_WAIT_1 |
dd .rst_close ;TCB_CLOSING |
dd .rst_close ;TCB_LAST_ACK |
dd .econnreset ;TCB_FIN_WAIT_2 |
dd .rst_close ;TCB_TIMED_WAIT |
|
.econnrefused: |
|
;;; TODO: debug info |
|
jmp .close |
|
.econnreset: |
|
;;; TODO: debug info |
.close: |
|
;;; update stats |
|
.rst_close: |
|
;;; Close the socket |
jmp .drop |
|
.rst_skip: |
|
;-------------------------------------- |
; handle SYN-full and ACK-less segments |
|
test [edx + TCP_segment.Flags], TH_SYN |
jz @f |
|
;;; tcp_drop ( ECONNRESET) |
jmp .drop_with_reset |
|
test [edx + TCP_segment.Flags], TH_ACK |
jz .drop |
|
;---------------- |
; Process the ACK |
|
cmp [ebx + TCP_SOCKET.t_state], TCB_SYN_RECEIVED |
jg .ack_dup |
jl .ack_nodup |
|
; dd .ack_nodup ;TCB_CLOSED |
; dd .ack_nodup ;TCB_LISTEN |
; dd .ack_nodup ;TCB_SYN_SENT |
; dd .ack_syn_rcvd ;TCB_SYN_RECEIVED |
; dd .ack_dup ;TCB_ESTABLISHED |
; dd .ack_dup ;TCB_CLOSE_WAIT |
; dd .ack_dup ;TCB_FIN_WAIT_1 |
; dd .ack_dup ;TCB_CLOSING |
; dd .ack_dup ;TCB_LAST_ACK |
; dd .ack_dup ;TCB_FIN_WAIT_2 |
; dd .ack_dup ;TCB_TIMED_WAIT |
|
;;;;; |
|
.ack_dup: |
|
;;;; |
|
.ack_nodup: |
|
;;;; 887 |
|
;------------------------------------------------- |
; If the congestion window was infalted to account |
; for the other side's cached packets, retrace it |
|
;;;; 888 - 902 |
|
|
;------------------------------------------ |
; RTT measurements and retransmission timer |
|
;;;;; 903 - 926 |
|
|
;------------------------------------------- |
; Open congestion window in response to ACKs |
|
;;;; |
|
|
;------------------------------------------ |
; Remove acknowledged data from send buffer |
|
;;;; 943 - 956 |
|
;--------------------------------------- |
; Wake up process waiting on send buffer |
|
;;;;; |
|
mov eax, [ebx + TCP_SOCKET.t_state] |
shl eax, 2 |
jmp dword [eax + .ACK_sw_list] |
|
.ACK_sw_list: |
dd .step6 ;TCB_CLOSED |
dd .step6 ;TCB_LISTEN |
dd .step6 ;TCB_SYN_SENT |
dd .step6 ;TCB_SYN_RECEIVED |
dd .step6 ;TCB_ESTABLISHED |
dd .step6 ;TCB_CLOSE_WAIT |
dd ._963 ;TCB_FIN_WAIT_1 |
dd ._958 ;TCB_CLOSING |
dd ._999 ;TCB_LAST_ACK |
dd .step6 ;TCB_FIN_WAIT_2 |
dd ._1010 ;TCB_TIMED_WAIT |
|
|
._963: |
|
|
jmp .step6 |
|
|
._958: |
|
jmp .step6 |
|
._999: |
|
jmp .step6 |
|
|
._1010: |
|
jmp .step6 |
|
|
|
align 4 |
.step6: |
|
DEBUGF 1,"step 6\n" |
|
;-------------------------- |
; update window information |
|
test [edx + TCP_segment.Flags], TH_ACK |
jz .no_window_update |
|
mov eax, [ebx + TCP_SOCKET.SND_WL1] |
cmp eax, [edx + TCP_segment.SequenceNumber] |
|
;;;; 1021 |
|
;---------------------------------- |
; Keep track of pure window updates |
|
test ecx, ecx |
jz @f |
|
mov eax, [ebx + TCP_SOCKET.SND_WL2] |
cmp eax, [edx + TCP_segment.AckNumber] |
jne @f |
|
;; mov eax, tiwin |
cmp eax, [ebx + TCP_SOCKET.SND_WND] |
jle @f |
|
;;; update stats |
|
@@: |
|
;; mov eax, incoming window |
cmp eax, [ebx + TCP_SOCKET.max_sndwnd] |
jle @f |
mov [ebx + TCP_SOCKET.max_sndwnd], eax |
@@: |
mov [ebx + TCP_SOCKET.SND_WND], eax |
|
mov eax, [edx + TCP_segment.SequenceNumber] |
mov [ebx + TCP_SOCKET.SND_WL1], eax |
|
mov eax, [edx + TCP_segment.AckNumber] |
mov [ebx + TCP_SOCKET.SND_WL2], eax |
|
;;; needoutput = 1 |
|
.no_window_update: |
|
|
;----------------- |
; process URG flag |
|
test [edx + TCP_segment.Flags], TH_URG |
jz .not_urgent |
|
cmp [edx + TCP_segment.UrgentPointer], 0 |
jz .not_urgent |
|
cmp [ebx + TCP_SOCKET.t_state], TCB_TIMED_WAIT |
je .not_urgent |
|
; Ignore bogus urgent offsets |
|
;;; 1040-1050 |
|
movzx eax, [edx + TCP_segment.UrgentPointer] |
add eax, [ebx + SOCKET.SO_RCV.SB_CC] |
cmp eax, SOCKET_MAXDATA |
jle .not_urgent |
|
mov [edx + TCP_segment.UrgentPointer], 0 |
and [edx + TCP_segment.Flags], not (TH_URG) |
jmp .do_data |
|
.not_urgent: |
|
;-------------------------------------- |
; processing of received urgent pointer |
|
;;; 1051-1093 |
|
align 4 |
.do_data: |
|
DEBUGF 1,"Do data:\n" |
|
; process the data in the segment |
|
test [edx + TCP_segment.Flags], TH_FIN |
jz .process_fin |
|
test [ebx + TCP_SOCKET.t_state], TCB_FIN_WAIT_1 ;;;;; |
jge .dont_do_data |
|
DEBUGF 1,"Processing data in segment\n" |
|
;;; NOW, process the data |
|
jmp .final_processing |
|
|
.dont_do_data: |
|
|
;--------------- |
; FIN processing |
|
.process_fin: |
|
DEBUGF 1,"Processing FIN\n" |
|
mov eax, [ebx + TCP_SOCKET.t_state] |
shl eax, 2 |
jmp dword [eax + .FIN_sw_list] |
|
.FIN_sw_list: |
dd .no_fin ;TCB_CLOSED |
dd .no_fin ;TCB_LISTEN |
dd .no_fin ;TCB_SYN_SENT |
dd ._1131 ;TCB_SYN_RECEIVED |
dd ._1131 ;TCB_ESTABLISHED |
dd .no_fin ;TCB_CLOSE_WAIT |
dd ._1139 ;TCB_FIN_WAIT_1 |
dd .no_fin ;TCB_CLOSING |
dd .no_fin ;TCB_LAST_ACK |
dd ._1147 ;TCB_FIN_WAIT_2 |
dd ._1156 ;TCB_TIMED_WAIT |
|
|
|
._1131: |
|
._1139: |
|
._1147: |
|
._1156: |
|
|
.no_fin: |
|
;----------------- |
; Final processing |
|
.final_processing: |
|
DEBUGF 1,"Final processing\n" |
|
;;; if debug enabled, output packet |
|
;test ;;;needoutput = 1 |
;jnz .outputnow |
|
test [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
jz .ret |
|
.outputnow: |
call TCP_output |
|
.ret: |
mov [ebx + SOCKET.lock], 0 |
|
call kernel_free |
add esp, 4 |
ret 4 |
|
ret |
;------------------------------------------ |
; Generate an ACK, droping incoming segment |
|
.found_it: ; eax points to empty queue entry |
align 4 |
.drop_after_ack: |
|
mov [eax + tcp_out_queue_entry.retries], TCP_RETRIES |
pop [eax + tcp_out_queue_entry.data_ptr] |
pop [eax + tcp_out_queue_entry.data_size] |
mov [eax + tcp_out_queue_entry.ttl], 1 ; send immediately |
mov [eax + tcp_out_queue_entry.owner], ebx |
mov [eax + tcp_out_queue_entry.sendproc], esi |
mov [eax + tcp_out_queue_entry.seq_num], edx |
mov [eax + tcp_out_queue_entry.socket], edi |
DEBUGF 1,"Drop after ACK\n" |
|
inc [TCP_OUT_QUEUE] |
test [edx + TCP_segment.Flags], TH_RST |
jnz .drop |
|
sub eax, TCP_OUT_QUEUE+8 |
shr eax, 5 |
DEBUGF 1,"Added to queue in pos %u, total queued packets: %u\n", eax, [TCP_OUT_QUEUE+8] |
and [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
|
mov [TCP_OUT_QUEUE+4], 0 |
call TCP_output |
|
ret |
mov [ebx + SOCKET.lock], 0 |
|
call kernel_free |
ret 4 |
|
;----------------------------------------------------------------- |
|
;------------------------------------------- |
; Generate an RST, dropping incoming segment |
|
align 4 |
.drop_with_reset: |
|
DEBUGF 1,"Drop with reset\n" |
|
test [edx + TCP_segment.Flags], TH_RST |
jnz .drop |
|
;;; if its a multicast/broadcast, also drop |
|
test [edx + TCP_segment.Flags], TH_ACK |
jnz .respond_ack |
|
test [edx + TCP_segment.Flags], TH_SYN |
jnz .respond_syn |
|
mov [ebx + SOCKET.lock], 0 |
|
call kernel_free |
ret 4 |
|
.respond_ack: |
|
;;;; |
|
call TCP_respond |
|
jmp .destroy_new_socket |
|
|
.respond_syn: |
|
;;;; |
|
call TCP_respond |
|
jmp .destroy_new_socket |
|
;----- |
; Drop |
|
align 4 |
.drop: |
|
DEBUGF 1,"Dropping packet\n" |
|
;;;; If debugging options are enabled, output the packet somwhere |
|
.destroy_new_socket: |
|
;;;; kill the newly created socket |
|
mov [ebx + SOCKET.lock], 0 |
|
call kernel_free |
ret 4 |
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
|
|
;--------------------- |
; |
; IN: ebx = socket |
; ecx = ack number |
; TCP_do_options |
; |
; OUT: / |
; |
;----------------------------------------------------------------- |
;------------------- |
|
align 4 |
TCP_queue_ack: |
TCP_do_options: |
|
DEBUGF 1,"Adding ACK to TCP queue, socket: %x, acknum: %u\n", ebx, ecx |
DEBUGF 1,"TCP_do_options\n" |
|
cmp [TCP_OUT_QUEUE], TCP_QUEUE_SIZE |
jge .full |
push eax |
sub eax, 20 |
jz .no_options |
|
push ebx ecx |
mov ebx, TCP_OUT_QUEUE+4 |
call wait_mutex |
lea esi, [edx + TCP_segment.Data] |
|
mov ecx, TCP_QUEUE_SIZE |
mov eax, TCP_OUT_QUEUE+8 |
|
;------------------------------------------- |
; Begin the loop by checking for EOL and NOP |
|
.loop: |
cmp [eax + tcp_out_queue_entry.data_ptr], 0 |
je .found_it |
add eax, tcp_out_queue_entry.size |
loop .loop |
|
add esp, 8 |
.full: ; silently discard the packet |
DEBUGF 1,"TCP queue is full!\n" |
ret |
cmp byte [esi], TCP_OPT_EOL ; end of option list? |
jz .no_options |
|
.found_it: ; eax points to empty queue entry |
cmp byte [esi], TCP_OPT_NOP ; nop ? |
;;; cmove edi, 1 ; if so, set option size to 1 |
jz .continue ; and continue scanning |
|
pop [eax + tcp_out_queue_entry.data_size] ; ACK number |
mov [eax + tcp_out_queue_entry.data_ptr], -1 ; ACK packet |
pop [eax + tcp_out_queue_entry.socket] |
mov [eax + tcp_out_queue_entry.retries], 1 |
mov [eax + tcp_out_queue_entry.ttl], 20 ; 200 ms |
;------------------ |
; We have an option |
|
inc [TCP_OUT_QUEUE] |
movzx edi, byte [esi + 1] ; get the length of this option in edi |
|
sub eax, TCP_OUT_QUEUE+8 |
shr eax, 5 |
DEBUGF 1,"Added to queue in pos %u, total queued packets: %u\n", eax, [TCP_OUT_QUEUE+8] |
|
mov [TCP_OUT_QUEUE+4], 0 |
;-------------------------------------- |
; Check for Maximum segment size option |
|
ret |
cmp byte [esi], TCP_OPT_MAXSEG |
jne .no_maxseg |
|
cmp edi, 4 ; option length |
jne .continue |
|
; IN: eax = socket pointer |
; ebx = device structure |
; ecx = ack number |
test [edx + TCP_segment.Flags], TH_SYN |
jz .continue |
|
align 4 |
TCP_send_ack: |
; Now parse the option... |
|
DEBUGF 1,"Creating TCP ACK packet, socket: %x, acknum: %x\n", eax, ecx |
jmp .continue |
|
push ecx eax |
.no_maxseg: |
|
mov di , IP_PROTO_TCP |
mov ecx, TCP_Packet.Data |
; Create an IPv4 Packet of the correct size |
mov ebx, [eax + SOCKET_head.end + IPv4_SOCKET.LocalIP] |
mov eax, [eax + SOCKET_head.end + IPv4_SOCKET.RemoteIP] |
;------------------------ |
; Check for Window option |
|
call IPv4_create_packet |
cmp edi, -1 |
je .fail |
cmp byte [esi], TCP_OPT_WINDOW |
jne .no_window |
|
pop ecx |
cmp edi, 3 ; option length |
jne .continue |
|
; fill in tcp sequence number |
push [ecx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT] |
pop [edi + TCP_Packet.SequenceNumber] |
test [edx + TCP_segment.Flags], TH_SYN |
jz .continue |
|
; Fill in local and remote ports |
push dword [ecx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.LocalPort] |
pop dword [edi + TCP_Packet.SourcePort] |
; ... |
|
; Acknumber |
pop [edi + TCP_Packet.AckNumber] |
jmp .continue |
|
; Fill in other tcp options |
mov [edi + TCP_Packet.Flags], TH_ACK |
mov [edi + TCP_Packet.Window], 0x0005 ; 1280 bytes |
mov [edi + TCP_Packet.UrgentPointer], 0 |
mov [edi + TCP_Packet.DataOffset], 0x50 |
mov [edi + TCP_Packet.Checksum], 0 |
.no_window: |
|
; Push pointer to and size of total packet (needed for send procedure) |
push edx eax esi |
;--------------------------- |
; Check for Timestamp option |
|
; Now, calculate the checksum |
pushw TCP_Packet.Data shl 8 |
pushw IP_PROTO_TCP shl 8 |
pushd [edi-4] ; destination address ; TODO: fix this, IPv4 packet could have options.. |
pushd [edi-8] ; source address |
cmp byte [esi], TCP_OPT_TIMESTAMP |
jne .no_timestamp |
|
xor edx, edx |
mov ecx, 12 |
mov esi, esp |
call checksum_1 |
call checksum_2 |
mov [edi + TCP_Packet.Checksum], dx |
add esp, 12 ; remove the pseudoheader from stack |
cmp edi, 10 ; option length |
jne .continue |
|
; ... |
|
|
jmp .continue |
|
.no_timestamp: |
|
;---------------------------------- |
; Future options may be placed here |
|
|
|
|
;------------------------------ |
; Continue scanning for options |
|
.continue: |
add esi, edi |
sub eax, edi |
jg .loop |
|
.no_options: |
|
pop eax |
call eax |
call kernel_free |
add esp, 4 ; pop (balance stack) |
|
ret |
|
.fail: |
add esp, 8 |
|
|
|
;--------------------------- |
; |
; TCP_pull_out_of_band |
; |
; IN: eax = |
; ebx = socket ptr |
; edx = tcp packet ptr |
; |
; OUT: / |
; |
;--------------------------- |
|
align 4 |
TCP_pull_out_of_band: |
|
DEBUGF 1,"TCP_pull_out_of_band\n" |
|
;;;; 1282-1305 |
|
ret |
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
|
|
|
|
;----------------------------------------------------------------- |
; |
; Remove all queued TCP packets for a specified socket |
; TCP_output |
; |
; IN: eax = socket number |
; IN: eax = socket pointer |
;; esi = ptr to data |
;; ecx = number of data bytes |
; |
; OUT: / |
; |
; destoys esi and ecx |
; |
;----------------------------------------------------------------- |
|
align 4 |
TCP_remove_socket: |
TCP_output: |
|
cmp [TCP_OUT_QUEUE], 0 |
je .skip |
DEBUGF 1,"TCP_output, socket: %x\n", eax |
|
mov ebx, TCP_OUT_QUEUE+4 |
call wait_mutex |
; 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 eax, TCP_QUEUE_SIZE |
mov ecx, [TCP_OUT_QUEUE] |
mov esi, TCP_OUT_QUEUE+8 |
mov ebx, [eax + TCP_SOCKET.SND_MAX] |
cmp ebx, [eax + TCP_SOCKET.SND_UNA] |
jne .not_idle |
|
.loop: |
cmp [esi + tcp_out_queue_entry.data_ptr], 0 |
jz .maybenext |
cmp [esi + tcp_out_queue_entry.socket], eax |
jnz .maybenext |
mov ebx, [eax + TCP_SOCKET.t_idle] |
cmp ebx, [eax + TCP_SOCKET.t_rxtcur] |
jle .not_idle |
|
push [esi + tcp_out_queue_entry.data_ptr] |
mov [esi + tcp_out_queue_entry.data_ptr], 0 |
dec [TCP_OUT_QUEUE] |
call kernel_free |
; 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. |
|
.maybenext: |
add esi, tcp_out_queue_entry.size |
loop .loop |
mov ebx, [eax + TCP_SOCKET.t_maxseg] |
mov [eax + TCP_SOCKET.SND_CWND], ebx |
|
mov [TCP_OUT_QUEUE+4], 0 |
.skip: |
ret |
.not_idle: |
.again: |
mov ebx, [eax + TCP_SOCKET.SND_NXT] ; calculate offset |
sub ebx, [eax + TCP_SOCKET.SND_UNA] ; |
|
mov ecx, [eax + TCP_SOCKET.SND_WND] ; determine window |
cmp ecx, [eax + TCP_SOCKET.SND_CWND] ; |
jl @f ; |
mov ecx, [eax + TCP_SOCKET.SND_CWND] ; |
@@: ; |
|
call TCP_outflags |
|
; 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 |
|
test [eax + TCP_SOCKET.t_force], -1 |
jz .no_persist_timeout |
|
;---------- TCB state handlers start here |
test ecx, ecx |
jnz .no_zero_window |
|
cmp ebx, [eax + SOCKET.SO_SND.SB_CC] |
jge @f |
|
and dl, not (TH_FIN) ; clear the FIN flag ??? how can it be set before? |
|
@@: |
inc ecx |
jmp .no_persist_timeout |
|
align 4 |
stateTCB_LISTEN: |
.no_zero_window: |
|
DEBUGF 1,"TCBStateHandler: Listen\n" |
;;; mov [eax + TCP_SOCKET.t_timer....TCPT_PERSIST], 0 |
mov [eax + TCP_SOCKET.t_rxtshift], 0 |
|
test [edx + TCP_Packet.Flags], TH_SYN ; SYN packet? => send syn+ack, open new socket and set connection to established |
jz .exit |
; Exit if backlog queue is full |
mov ax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.backlog_cur] |
cmp ax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.backlog] |
jae .exit |
; Allocate new socket |
push esi edi |
call net_socket_alloc |
test eax, eax |
jz .fail |
; Copy structure from current socket to new, including lock |
lea esi, [ebx + SOCKET_head.PID] ; yes, PID must also be copied |
lea edi, [eax + SOCKET_head.PID] |
mov ecx, ((SOCKET_head.end - SOCKET_head.PID) + IPv4_SOCKET.end + TCP_SOCKET.end + 3)/4 |
rep movsd |
pop edi esi |
; Push pointer to new socket to queue |
movzx ecx, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.backlog_cur] |
inc [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.backlog_cur] |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.end + ecx*4], eax |
.no_persist_timeout: |
|
mov [eax + SOCKET_head.end + IPv4_SOCKET.RemoteIP], esi ; IP source address |
mov cx, [edx + TCP_Packet.SourcePort] |
mov [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RemotePort], cx |
mov ecx, [edx + TCP_Packet.SequenceNumber] |
mov [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.IRS], ecx |
mov [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT], ecx |
lea esi, [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT] |
inc_INET esi ; RCV.NXT |
mov ecx, [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.ISS] |
mov [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT], ecx |
;;;106 |
|
mov [ebx + SOCKET_head.lock], 0 |
mov esi, [eax + SOCKET.SO_SND.SB_CC] |
cmp esi, ecx |
jl @f |
mov esi, ecx |
@@: |
sub esi, ebx |
|
push eax |
; Now construct the response |
mov bl, TH_SYN + TH_ACK |
xor ecx, ecx |
call TCP_send |
pop eax |
cmp esi, -1 |
jne .not_minus_one |
|
mov [eax + SOCKET_head.lock], 0 |
mov [eax + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_SYN_RECEIVED |
call notify_network_event |
ret |
; If FIN has been set, but not ACKed, and we havent been called to retransmit, |
; len (esi) will be -1 |
; Otherwise, window shrank after we sent into it. |
; If window shrank to 0, cancel pending retransmit and pull SND_NXT back to (closed) window |
; We will enter persist state below. |
; If window didn't close completely, just wait for an ACK |
|
.exit: |
mov [ebx + SOCKET_head.lock], 0 |
ret |
xor esi, esi |
|
.fail: |
add esp, 8 |
mov [ebx + SOCKET_head.lock], 0 |
ret |
test ecx, ecx |
jnz @f |
|
;;; mov [eax + TCP_SOCKET.t_timer..TCPT_REXMT], 0 |
|
align 4 |
stateTCB_SYN_SENT: |
push [eax + TCP_SOCKET.SND_UNA] |
pop [eax + TCP_SOCKET.SND_NXT] |
@@: |
|
DEBUGF 1,"TCBStateHandler: Syn_Sent\n" |
.not_minus_one: |
|
; We are awaiting an ACK to our SYN, with a SYM |
; Look at control flags - expecting an ACK |
;;; 124 |
|
mov al, [edx + TCP_Packet.Flags] |
cmp esi, [eax + TCP_SOCKET.t_maxseg] |
jle @f |
|
test al, TH_RST |
jnz .reset ; jump if RST bit set |
mov esi, [eax + TCP_SOCKET.t_maxseg] |
;sendalot = 1 |
|
push [edx + TCP_Packet.SequenceNumber] ;; |
pop [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT] ;; |
inc_INET (ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT) ;; |
@@: |
|
;;; 128 |
|
push [edx + TCP_Packet.AckNumber] ;;;;;; |
pop [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.SND_NXT] ;;;;;; |
mov edi, [eax + TCP_SOCKET.SND_NXT] |
add edi, esi ; len |
sub edi, [eax + TCP_SOCKET.SND_UNA] |
add edi, [eax + SOCKET.SO_SND.SB_CC] |
cmp edi, 0 |
jle @f |
|
and al, TH_SYN + TH_ACK |
jz .exit ; jump if none of the following is set: RST, SYN, ACK |
and dl, not (TH_FIN) ; clear the FIN flag |
|
test al, TH_ACK |
jz .onlysyn ; jump if only SYN bit is set |
@@: |
|
; If we arrived here, SYN and ACK are set |
|
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_ESTABLISHED |
pushw TH_ACK |
;;;; 130 TODO: set window (ecx) to space in send buffer |
|
.send: ; Send an ACK |
mov eax, ebx |
pop bx |
push eax |
xor ecx, ecx |
call TCP_send |
pop ebx |
|
.exit: |
mov [ebx + SOCKET_head.lock], 0 |
ret |
;------------------------------ |
; Sender silly window avoidance |
|
.reset: |
; TODO: .... |
test esi, esi |
jz .zero_length |
|
; remove all queued TCP packets for this connection ! |
|
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_CLOSED |
mov [ebx + SOCKET_head.lock], 0 |
ret |
cmp esi, [eax + TCP_SOCKET.t_maxseg] |
je .send |
|
.onlysyn: |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_SYN_RECEIVED |
pushw TH_SYN + TH_ACK |
jmp .send |
;;; TODO: 144-145 |
|
test [eax + TCP_SOCKET.t_force], -1 |
jnz .send |
|
;;; TODO: 149..152 |
|
align 4 |
stateTCB_SYN_RECEIVED: |
.zero_length: |
|
DEBUGF 1,"TCBStateHandler: Syn_received\n" |
|
test [edx + TCP_Packet.Flags], TH_RST ; reset connection? => LISTEN |
jz .check_ack |
;---------------------------------------- |
; Check if a window update should be sent |
|
push [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.OrigRemotePort] |
pop [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RemotePort] |
push [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.OrigRemoteIP] |
pop [ebx + SOCKET_head.end + IPv4_SOCKET.RemoteIP] |
cmp ecx, 0 ; window |
jle .no_window |
|
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_LISTEN |
jmp .exit |
;;; TODO 154-172 |
|
.check_ack: |
test [edx + TCP_Packet.Flags], TH_ACK ; ACK? => connection established! |
jz .exit |
.no_window: |
|
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_ESTABLISHED |
mov eax, ebx |
call notify_network_event |
;-------------------------- |
; Should a segment be sent? |
|
.exit: |
mov [ebx + SOCKET_head.lock], 0 |
ret |
test [ebx + TCP_SOCKET.t_flags], TF_ACKNOW |
jnz .send |
|
test dl, TH_SYN + TH_RST |
jnz .send |
|
if 0 |
mov eax, [ebx + TCP_SOCKET.SND_UP] |
cmp eax, [ebx + TCP_SOCKET.SND_UNA] |
jg .send |
|
test dl, TH_FIN |
jz .enter_persist |
|
align 4 |
stateTCB_ESTABLISHED: |
test [ebx + TCP_SOCKET.t_flags], TF_SENTFIN |
jnz .send |
|
DEBUGF 1,"TCBStateHandler: Established\n" |
mov eax, [ebx + TCP_SOCKET.SND_NXT] |
cmp eax, [ebx + TCP_SOCKET.SND_UNA] |
je .send |
|
mov eax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT] |
bswap eax |
DEBUGF 1,"RCV_NXT is set to:%u\n", eax |
bswap eax |
cmp eax, [edx + TCP_Packet.SequenceNumber] |
jne .exit ;;;;;; |
;-------------------- |
; Enter persist state |
|
; check if we received an ACK |
test [edx + TCP_Packet.Flags], TH_ACK |
jz .no_ack |
.enter_persist: |
|
mov ax, [edx + TCP_Packet.Window] |
xchg al, ah |
cmp ax, 1024 |
ja @f |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.wndsizeTimer], 1 |
@@: |
.no_ack: |
DEBUGF 1,"Entering pesist state\n" |
|
; Now, see if we received any data |
test ecx, ecx |
jz .nodata |
|
; Calculate next sequencenumber |
add_INET (ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT) |
|
push edx |
DEBUGF 1,"Got %u bytes data!\n", ecx |
; calculate header length |
movzx eax, [edx + TCP_Packet.DataOffset] |
and eax, 11110000b |
shr eax, 2 |
DEBUGF 1,"TCP header size: %u\n", eax |
add edx, eax ; now edx points to data |
;-------------------------------------- |
; No reason to send a segment, just ret |
|
add esp, 4 |
pop esi ; pointer to buffer |
add esp, 4 |
DEBUGF 1,"No reason to send a segment\n" |
|
sub edx, esi |
mov edi, edx ; offset |
mov eax, ebx ; socket ptr |
ret |
|
call socket_internal_receiver ; Place the data from packet into socket |
|
; lea ebx, [eax + SOCKET_head.lock] |
; call wait_mutex |
mov ebx, eax |
pop edx |
|
test [edx + TCP_Packet.Flags], TH_FIN + TH_RST |
jz .ack |
|
.nodata: |
test [edx + TCP_Packet.Flags], TH_FIN + TH_RST |
jz .exit |
|
; Send an ACK to that fin, and enter closewait state |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_CLOSE_WAIT |
; Remove all resend entries from the queue |
mov eax, ebx |
call TCP_remove_socket |
;----------------------------------------------- |
; |
; Send a segment |
; |
; ebx = socket pointer |
; dl = flags |
; |
;----------------------------------------------- |
|
.ack: |
push ebx |
mov ecx, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT] |
call TCP_queue_ack |
pop ebx |
.send: |
|
.exit: |
mov [ebx + SOCKET_head.lock], 0 |
ret |
DEBUGF 1,"Preparing to send a segment\n" |
|
xor edi, edi ; edi will contain the number of header option bytes |
|
end if |
;------------------------------------ |
; Send options with first SYN segment |
|
test dl, TH_SYN |
jz .no_options |
|
align 4 |
stateTCB_ESTABLISHED: |
mov eax, [ebx + TCP_SOCKET.ISS] |
mov [ebx + TCP_SOCKET.SND_NXT], eax |
|
DEBUGF 1,"TCBStateHandler: Established\n" |
test [ebx + TCP_SOCKET.t_flags], TF_NOOPT |
jnz .no_options |
|
mov eax, [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT] |
mov eax, TCP_OPT_MAXSEG shl 24 + 4 shl 16 |
mov ax, 1280 ;;;;;; |
bswap eax |
DEBUGF 1,"RCV_NXT is set to:%u\n", eax |
push eax |
|
mov di, 4 |
|
test [ebx + TCP_SOCKET.t_flags], TF_REQ_SCALE |
jz .no_syn |
|
test dl, TH_ACK |
jnz .scale_opt |
|
test [ebx + TCP_SOCKET.t_flags], TF_RCVD_SCALE |
jz .no_syn |
|
.scale_opt: |
|
mov eax, TCP_OPT_WINDOW shl 24 + 4 shl 16 + TCP_OPT_NOP |
mov ah, byte [ebx + TCP_SOCKET.request_r_scale] |
bswap eax |
cmp eax, [edx + TCP_Packet.SequenceNumber] |
jne .exit |
push eax |
|
; Calculate next sequencenumber |
add_INET (ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.RCV_NXT) |
add di, 4 |
|
test [edx + TCP_Packet.Flags], TH_FIN + TH_RST |
jnz .fin |
.no_syn: |
|
.check_ack: |
test [edx + TCP_Packet.Flags], TH_ACK |
jz .exit |
;------------------------------------ |
; Make the timestamp option if needed |
|
DEBUGF 1,"Received ACK\n" |
; First, look at the incoming window. If this is less than or equal to 1024, |
; Set the socket window timer to 1. This will stop an additional Packets being queued. |
; ** I may need to tweak this value, since I do not know how many Packets are already queued |
push ecx |
mov cx, [edx + TCP_Packet.Window] |
xchg cl, ch |
cmp cx, 1024 |
ja @f |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.wndsizeTimer], 1 |
@@: |
pop ecx |
test [ebx + TCP_SOCKET.t_flags], TF_REQ_TSTMP |
jz .no_timestamp |
|
; Now, see if we received any data |
test dl, TH_RST |
jnz .no_timestamp |
|
test dl, TH_ACK |
jz .timestamp |
|
test [ebx + TCP_SOCKET.t_flags], TF_RCVD_TSTMP |
jz .no_timestamp |
|
.timestamp: |
|
DEBUGF 1,"Creating a timestamp\n" |
|
push dword (TCP_OPT_TIMESTAMP shl 8 + 10 + TCP_OPT_NOP shl 16 + TCP_OPT_NOP shl 24) |
pushw 0 |
mov eax, [timer_ticks] |
bswap eax |
push eax |
|
add di, 10 |
|
.no_timestamp: |
|
;; TODO: check if we dont exceed the max segment size |
|
.no_options: |
add edi, TCP_segment.Data |
|
;----------------------------------- |
; Check if we have some data to send |
|
;;; mov ecx, [huppeldepup] |
|
test ecx, ecx |
jz .exit |
jz .no_data |
|
DEBUGF 1,"Got %u bytes data!\n", ecx |
; calculate header length |
movzx eax, [edx + TCP_Packet.DataOffset] |
and eax, 11110000b |
shr eax, 2 |
DEBUGF 1,"TCP header size: %u\n", eax |
add edx, eax ; now edx points to data |
;;; 278-316 |
|
add esp, 4 |
pop esi ; pointer to buffer |
add esp, 4 |
jmp .header |
|
sub edx, esi |
mov edi, edx ; offset |
mov eax, ebx ; socket ptr |
.no_data: |
|
call socket_internal_receiver ; Place the data from packet into socket |
;;; 317-338 |
|
lea ebx, [eax + SOCKET_head.lock] |
call wait_mutex |
mov ebx, eax |
|
.ack: |
mov eax, ebx |
mov bl, TH_ACK |
push eax |
xor ecx, ecx |
call TCP_send ; send the ack |
pop ebx |
.exit: |
mov [ebx + SOCKET_head.lock], 0 |
;---------- |
|
push di dx ebx |
|
add ecx, edi ; total TCP segment size |
|
mov eax, [ebx + IP_SOCKET.RemoteIP] |
mov ebx, [ebx + IP_SOCKET.LocalIP] |
mov di , IP_PROTO_TCP |
call IPv4_create_packet |
|
;;;; jz .fail |
|
push edx eax |
call NET_send |
ret |
|
.fin: ; we received a FIN or RESET |
; Remove all resend entries from the queue |
mov ecx, TCP_QUEUE_SIZE |
mov esi, TCP_OUT_QUEUE+4 |
;---------------- |
|
.removeloop: |
cmp [esi + tcp_out_queue_entry.data_ptr], 0 |
je .maybe_next |
|
; TODO: check if the packets belong to the same tcp connection ! |
;------------------------------- |
; Now, create the 20-byte header |
|
DEBUGF 1,"Removing a queued packet\n" |
.header: |
|
push [esi + tcp_out_queue_entry.data_ptr] |
mov [esi + tcp_out_queue_entry.data_ptr], 0 |
dec [TCP_OUT_QUEUE] |
call kernel_free |
;----------------------- |
; Fill in the TCP header |
pop esi |
|
.maybe_next: |
add esi, tcp_out_queue_entry.size |
loop .removeloop |
push [esi + TCP_SOCKET.SND_NXT] |
rol word [esp], 8 |
rol dword [esp], 16 |
pop [edi + TCP_segment.SequenceNumber] |
|
; Send an ACK to that fin, and enter closewait state |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_CLOSING |
jmp .check_ack |
push [esi + TCP_SOCKET.RCV_NXT] |
rol word [esp], 8 |
rol dword [esp], 16 |
pop [edi + TCP_segment.AckNumber] |
|
push [esi + TCP_SOCKET.LocalPort] |
rol word [esp], 8 |
pop [edi + TCP_segment.SourcePort] |
|
align 4 |
stateTCB_FIN_WAIT_1: |
push [esi + TCP_SOCKET.RemotePort] |
rol word [esp], 8 |
pop [edi + TCP_segment.DestinationPort] |
|
DEBUGF 1,"TCBStateHandler: Fin_wait_1\n" |
|
; We can either receive an ACK of a fin, or a fin |
mov al, [edx + TCP_Packet.Flags] |
and al, TH_FIN + TH_ACK |
mov [edi + TCP_segment.Window], 0x0005 |
; 1280 bytes |
mov [edi + TCP_segment.UrgentPointer], 0 |
|
cmp al, TH_ACK |
jne @f |
mov [edi + TCP_segment.DataOffset], 0x50 |
|
; It was an ACK |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_FIN_WAIT_2 |
jmp .exit |
mov [edi + TCP_segment.Flags], cl |
|
@@: mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_CLOSING |
cmp al, TH_FIN |
je @f |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_TIMED_WAIT |
mov [edi + TCP_segment.Checksum], 0 |
|
@@: |
; Send an ACK |
mov eax, ebx |
mov bl, TH_ACK |
push eax |
xor ecx, ecx |
call TCP_send |
pop ebx |
;----- |
|
.exit: |
mov [ebx + SOCKET_head.lock], 0 |
ret |
|
;-------------- |
; Copy the data |
|
pop esi |
push edi |
add edi, TCP_segment.Data ;; |
sub ecx, TCP_segment.Data ;;; |
|
align 4 |
stateTCB_FIN_WAIT_2: |
shr ecx, 1 |
jnc .nb |
movsb |
.nb: |
shr ecx, 1 |
jnc .nw |
movsw |
.nw: |
test ecx, ecx |
jz .nd |
rep movsd |
.nd: |
pop edi |
|
DEBUGF 1,"TCBStateHandler: Fin_wait_2\n" |
;-------------------- |
; Create the checksum |
|
test [edx + TCP_Packet.Flags], TH_FIN |
jz .exit |
push [ebx + IP_SOCKET.LocalIP] |
push [ebx + IP_SOCKET.RemoteIP] |
call TCP_checksum |
|
; Change state, as we have a fin |
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_TIMED_WAIT |
;---------------- |
; Send the packet |
|
; Send an ACK |
mov eax, ebx |
mov bl, TH_ACK |
push eax |
xor ecx, ecx |
call TCP_send |
pop ebx |
;;;;; |
|
.exit: |
mov [ebx + SOCKET_head.lock], 0 |
|
DEBUGF 1,"Sending TCP Packet to device %x\n", ebx |
mov esi, [ebx + ETH_DEVICE.transmit] |
|
ret |
|
|
|
;------------------------- |
; |
; TCP_outflags |
; |
; IN: eax = socket ptr |
; |
; OUT: edx = flags |
; |
;------------------------- |
align 4 |
stateTCB_CLOSE_WAIT: |
TCP_outflags: |
|
DEBUGF 1,"TCBStateHandler: close_wait\n" |
; Intentionally left empty |
; socket_close_tcp handles this |
mov edx, [eax + TCP_SOCKET.t_state] |
movzx edx, byte [edx + .flaglist] |
|
mov [ebx + SOCKET_head.lock], 0 |
DEBUGF 1,"TCP_outflags, socket: %x, flags: %x\n", eax, dl |
|
ret |
|
.flaglist: |
|
db TH_RST + TH_ACK ; TCB_CLOSED |
db 0 ; TCB_LISTEN |
db TH_SYN ; TCB_SYN_SENT |
db TH_SYN + TH_ACK ; TCB_SYN_RECEIVED |
db TH_ACK ; TCB_ESTABLISHED |
db TH_ACK ; TCB_CLOSE_WAIT |
db TH_SYN + TH_ACK ; TCB_FIN_WAIT_1 |
db TH_SYN + TH_ACK ; TCB_CLOSING |
db TH_SYN + TH_ACK ; TCB_LAST_ACK |
db TH_ACK ; TCB_FIN_WAIT_2 |
db TH_ACK ; TCB_TIMED_WAIT |
|
|
;------------------------- |
; |
; TCP_drop |
; |
; IN: eax = socket ptr |
; |
; OUT: / |
; |
;------------------------- |
align 4 |
stateTCB_CLOSING: |
TCP_drop: |
|
DEBUGF 1,"TCBStateHandler: closingn\n" |
DEBUGF 1,"TCP_drop\n" |
|
; We can either receive an ACK of a fin, or a fin |
test [edx + TCP_Packet.Flags], TH_ACK |
jz .exit |
; cmp [eax + TCP_SOCKET.t_state], TCB_SYN_RECEIVED |
; jl .no_syn_received |
|
mov [ebx + SOCKET_head.end + IPv4_SOCKET.end + TCP_SOCKET.state], TCB_TIMED_WAIT |
mov [eax + TCP_SOCKET.t_state], TCB_CLOSED |
|
.exit: |
call TCP_output |
|
mov [ebx + SOCKET_head.lock], 0 |
; .no_syn_received: |
|
ret |
|
|
align 4 |
stateTCB_LAST_ACK: |
|
DEBUGF 1,"TCBStateHandler: last_ackn\n" |
|
; Look at control flags - expecting an ACK |
test [edx + TCP_Packet.Flags], TH_ACK |
jz .exit |
|
mov [ebx + SOCKET_head.lock], 0 |
;--------------------------------------- |
; |
; TCP_respond |
; |
; The easy way to send a RST/ACK segment |
; |
; IN: eax = socket ptr |
; |
; OUT: / |
; |
;--------------------------------------- |
align 4 |
TCP_respond: |
|
; delete the socket |
stdcall net_socket_free, ebx |
DEBUGF 1,"TCP_respond\n" |
|
.exit: |
ret |
|
|
|
;----------------------------------------------------------------- |
; |
; TCP_checksum |
; |
; This is the fast procedure to create or check a UDP header |
; - 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 |
; |
; IN: push source ip |
; push dest ip |
; |
; esi = packet ptr |
; |
; OUT: checksum is filled in in packet! (but also in dx) |
; |
;----------------------------------------------------------------- |
align 4 |
stateTCB_TIME_WAIT: |
TCP_checksum: |
|
DEBUGF 1,"TCBStateHandler: time_wait\n" |
;------------- |
; Pseudoheader |
|
mov [ebx + SOCKET_head.lock], 0 |
; protocol type |
mov edx, IP_PROTO_TCP ; NO shl 8 here ! (it took me ages to figure this one out) |
|
ret |
; source address |
add dl, [esp+1+4] |
adc dh, [esp+0+4] |
adc dl, [esp+3+4] |
adc dh, [esp+2+4] |
|
; destination address |
adc dl, [esp+1+8] |
adc dh, [esp+0+8] |
adc dl, [esp+3+8] |
adc dh, [esp+2+8] |
|
align 4 |
stateTCB_CLOSED: |
; size |
adc dl, cl |
adc dh, ch |
|
DEBUGF 1,"TCBStateHandler: closed\n" |
;--------------------- |
; Real header and data |
|
mov [ebx + SOCKET_head.lock], 0 |
push esi |
call checksum_1 |
call checksum_2 |
pop esi |
|
ret |
neg [esi+UDP_Packet.Checksum] ; zero will stay zero so we just get the checksum |
add [esi+UDP_Packet.Checksum], dx ; , else we will get (new checksum - old checksum) in the end, wich should be 0 :) |
|
ret 8 ; Remove the IPs from stack |
|
|
|
|
;--------------------------------------------------------------------------- |
; |
; TCP_API |
1269,11 → 2007,11 |
ret |
|
.packets_tx: |
add eax, TCP_PACKETS_TX |
add eax, TCP_segments_tx |
mov eax, [eax] |
ret |
|
.packets_rx: |
add eax, TCP_PACKETS_RX |
add eax, TCP_segments_rx |
mov eax, [eax] |
ret |