kolibrios/kernel/branches/net/network/tcp_output.inc

410 lines
8.7 KiB
PHP
Raw Normal View History

;-----------------------------------------------------------------
;
; TCP_output
;
; IN: eax = socket pointer
;
; OUT: /
;
;-----------------------------------------------------------------
align 4
TCP_output:
DEBUGF 1,"TCP_output, socket: %x\n", eax
; 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]
jne .not_idle
mov ebx, [eax + TCP_SOCKET.t_idle]
cmp ebx, [eax + TCP_SOCKET.t_rxtcur]
jle .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 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 ; in dl
; 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
test ecx, ecx
jnz .no_zero_window
cmp ebx, [eax + STREAM_SOCKET.snd + RING_BUFFER.size]
jge @f
and dl, not (TH_FIN) ; clear the FIN flag ??? how can it be set before?
@@:
inc ecx
jmp .no_persist_timeout
.no_zero_window:
mov [eax + TCP_SOCKET.timer_persist], 0
mov [eax + TCP_SOCKET.t_rxtshift], 0
.no_persist_timeout:
;;;106
mov esi, [eax + STREAM_SOCKET.snd + RING_BUFFER.size]
cmp esi, ecx
jl @f
mov esi, ecx
@@:
sub esi, ebx
cmp esi, -1
jne .not_minus_one
; 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
xor esi, esi
test ecx, ecx
jnz @f
mov [eax + TCP_SOCKET.timer_retransmission], 0 ; cancel retransmit
push [eax + TCP_SOCKET.SND_UNA]
pop [eax + TCP_SOCKET.SND_NXT]
@@:
.not_minus_one:
;;; 124
cmp esi, [eax + TCP_SOCKET.t_maxseg]
jle @f
mov esi, [eax + TCP_SOCKET.t_maxseg]
;sendalot = 1
@@:
;;; 128
mov edi, [eax + TCP_SOCKET.SND_NXT]
add edi, esi ; len
sub edi, [eax + TCP_SOCKET.SND_UNA]
add edi, [eax + STREAM_SOCKET.snd + RING_BUFFER.size]
cmp edi, 0
jle @f
and dl, not (TH_FIN) ; clear the FIN flag
@@:
; set ecx to space available in receive buffer
; From now on, ecx will be the window we advertise to the other end
mov ecx, SOCKET_MAXDATA
sub ecx, [eax + STREAM_SOCKET.rcv + RING_BUFFER.size]
;------------------------------
; Sender silly window avoidance
cmp ecx, [eax + TCP_SOCKET.t_maxseg]
je .send
;;; TODO: 144-145
test [eax + TCP_SOCKET.t_force], -1
jnz .send
mov ebx, [eax + TCP_SOCKET.max_sndwnd]
shr ebx, 1
cmp ecx, ebx
jge .send
mov ebx, [eax + TCP_SOCKET.SND_NXT]
cmp ebx, [eax + TCP_SOCKET.SND_MAX]
jl .send
;----------------------------------------
; Check if a window update should be sent
test ecx, ecx ; window
jz .no_window
;;; TODO 154-172
.no_window:
;--------------------------
; Should a segment be sent?
test [eax + TCP_SOCKET.t_flags], TF_ACKNOW
jnz .send
test dl, TH_SYN + TH_RST
jnz .send
mov ebx, [eax + TCP_SOCKET.SND_UP]
cmp ebx, [eax + TCP_SOCKET.SND_UNA]
jg .send
test dl, TH_FIN
jz .enter_persist
test [eax + TCP_SOCKET.t_flags], TF_SENTFIN
jnz .send
mov ebx, [eax + TCP_SOCKET.SND_NXT]
cmp ebx, [eax + TCP_SOCKET.SND_UNA]
je .send
;--------------------
; Enter persist state
.enter_persist:
DEBUGF 1,"Entering persist state\n"
;--------------------------------------
; No reason to send a segment, just ret
DEBUGF 1,"No reason to send a segment\n"
mov [ebx + SOCKET.lock], 0
ret
;-----------------------------------------------
;
; Send a segment
;
; eax = socket pointer
; dl = flags
;
;-----------------------------------------------
.send:
DEBUGF 1,"Preparing to send a segment\n"
mov edi, TCP_segment.Data ; edi will contain headersize
sub esp, 8 ; create some space on stack
push eax ; save this too..
;------------------------------------
; Send options with first SYN segment
test dl, TH_SYN
jz .no_options
push [eax + TCP_SOCKET.ISS]
pop [eax + TCP_SOCKET.SND_NXT]
test [eax + TCP_SOCKET.t_flags], TF_NOOPT
jnz .no_options
mov ecx, 1460
or ecx, TCP_OPT_MAXSEG shl 24 + 4 shl 16
bswap ecx
push ecx
add di, 4
test [eax + TCP_SOCKET.t_flags], TF_REQ_SCALE
jz .no_syn
test dl, TH_ACK
jnz .scale_opt
test [eax + TCP_SOCKET.t_flags], TF_RCVD_SCALE
jz .no_syn
.scale_opt:
movzx ecx, byte [eax + TCP_SOCKET.request_r_scale]
or ecx, TCP_OPT_WINDOW shl 24 + 4 shl 16 + TCP_OPT_NOP shl 8
bswap ecx
pushd ecx
add di, 4
.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:
mov esi, [timer_ticks]
bswap esi
push esi
pushw 0
pushd TCP_OPT_TIMESTAMP + 10 shl 8 + TCP_OPT_NOP shl 16 + TCP_OPT_NOP shl 24
add di, 10
.no_timestamp:
;; TODO: check if we dont exceed the max segment size
.no_options:
; eax = socket ptr
; edx = flags
; ecx = data size
; edi = header size
; esi = snd ring buff ptr
mov ecx, [eax + STREAM_SOCKET.snd + RING_BUFFER.size]
cmp ecx, [eax + TCP_SOCKET.t_maxseg] ;;; right?
jle @f
mov ecx, [eax + TCP_SOCKET.t_maxseg]
@@:
add ecx, edi ; total TCP segment size
; Start by pushing all TCP header values in reverse order on stack
; (essentially, creating the tcp header!)
pushw 0 ; .UrgentPointer dw ?
pushw 0 ; .Checksum dw ?
pushw 0x00a0 ; .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 ?
ntohw [esp]
push [eax + TCP_SOCKET.LocalPort] ; .SourcePort dw ?
ntohw [esp]
push edi ; header size
; Create the IP packet
mov ebx, [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 .fail
;-----------------------------------------
; Move TCP header from stack to TCP packet
push ecx
mov ecx, [esp+4]
lea esi, [esp+4+4]
shr ecx, 2
rep movsd
pop ecx ; full TCP packet size
pop esi ; headersize
add esp, esi
mov [esp + 4], eax ; packet ptr
mov [esp + 4+4], edx ; packet size
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
; test ecx, ecx
mov eax, [esp+4] ; socket ptr
add [eax + TCP_SOCKET.SND_NXT], ecx
add eax, STREAM_SOCKET.snd
push edx
call SOCKET_ring_read
pop esi
pop ecx
pop eax
test [esi + TCP_segment.Flags], TH_SYN + TH_FIN
jz @f
inc [eax + TCP_SOCKET.SND_NXT]
;;; TODO: update sentfin flag
@@:
mov edx, [eax + TCP_SOCKET.SND_NXT]
cmp edx, [eax + TCP_SOCKET.SND_MAX]
jle @f
mov [eax + TCP_SOCKET.SND_MAX], edx
;;;; TODO: time transmission (420)
@@:
;;; TODO: set retransmission timer
;--------------------
; Create the checksum
DEBUGF 1,"checksum: ptr=%x size=%u\n", esi, ecx
TCP_checksum (eax + IP_SOCKET.LocalIP), (eax + IP_SOCKET.RemoteIP)
mov [esi+TCP_segment.Checksum], dx
;----------------
; Send the packet
DEBUGF 1,"Sending TCP Packet to device %x\n", ebx
call [ebx + NET_DEVICE.transmit]
ret
.fail:
pop ecx
add esp, ecx
add esp, 4+8
DEBUGF 1,"TCP_output: failed\n"
ret