* Updates in TCP code

* Splitted TCP code into multiple files
* cleanup

git-svn-id: svn://kolibrios.org@1733 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
hidnplayr 2011-01-08 14:59:21 +00:00
parent e0fed2fb2e
commit 1dd8c78cbd
8 changed files with 2267 additions and 2112 deletions

View File

@ -205,11 +205,6 @@ ETH_API:
movzx eax, bh movzx eax, bh
shl eax, 2 shl eax, 2
cmp bl, 7
jz .out_queue
cmp bl, 6
jz .in_queue
mov eax, dword [NET_DRV_LIST + eax] mov eax, dword [NET_DRV_LIST + eax]
cmp [eax + NET_DEVICE.type], NET_TYPE_ETH cmp [eax + NET_DEVICE.type], NET_TYPE_ETH
jne .error jne .error
@ -266,20 +261,3 @@ ETH_API:
call [eax + ETH_DEVICE.set_MAC] call [eax + ETH_DEVICE.set_MAC]
ret ret
.in_queue:
if ETH_QUEUE
add eax, ETH_IN_QUEUE
mov eax, [eax + queue.size]
else
or eax, -1
end if
ret
.out_queue:
if ETH_QUEUE
add eax, ETH_OUT_QUEUE
mov eax, [eax + queue.size]
else
or eax, -1
end if
ret

View File

@ -508,19 +508,15 @@ align 4
mov [eax + TCP_SOCKET.timer_persist], 0 mov [eax + TCP_SOCKET.timer_persist], 0
mov [eax + TCP_SOCKET.t_state], TCB_SYN_SENT mov [eax + TCP_SOCKET.t_state], TCB_SYN_SENT
mov ebx, [TCP_sequence_num] push [TCP_sequence_num]
add [TCP_sequence_num], 6400 add [TCP_sequence_num], 6400
mov [eax + TCP_SOCKET.ISS], ebx pop [eax + TCP_SOCKET.ISS]
mov [eax + TCP_SOCKET.timer_keepalive], TCP_time_keep_init mov [eax + TCP_SOCKET.timer_keepalive], TCP_time_keep_init
TCP_sendseqinit eax TCP_sendseqinit eax
; mov [ebx + TCP_SOCKET.timer_retransmission], ;; todo: create macro to set retransmission timer ; mov [ebx + TCP_SOCKET.timer_retransmission], ;; todo: create macro to set retransmission timer
push eax
call TCP_output
pop eax
mov ebx, eax mov ebx, eax
lea eax, [ebx + STREAM_SOCKET.snd] lea eax, [ebx + STREAM_SOCKET.snd]
@ -530,6 +526,10 @@ align 4
call SOCKET_ring_create call SOCKET_ring_create
mov [ebx + SOCKET.lock], 0 mov [ebx + SOCKET.lock], 0
mov eax, ebx
call TCP_output
mov dword [esp+32], 0 mov dword [esp+32], 0
ret ret

View File

@ -20,6 +20,7 @@
$Revision$ $Revision$
__DEBUG_LEVEL_OLD__ equ __DEBUG_LEVEL__ __DEBUG_LEVEL_OLD__ equ __DEBUG_LEVEL__
__DEBUG_LEVEL__ equ 1 ; this sets the debug level for network part of kernel __DEBUG_LEVEL__ equ 1 ; this sets the debug level for network part of kernel
uglobal uglobal
@ -29,14 +30,12 @@ endg
MAX_NET_DEVICES equ 16 MAX_NET_DEVICES equ 16
ETH_QUEUE equ 0 ; 1 = enable / 0 = disable
MIN_EPHEMERAL_PORT equ 49152 MIN_EPHEMERAL_PORT equ 49152
MAX_EPHEMERAL_PORT equ 61000 MAX_EPHEMERAL_PORT equ 61000
; Ethernet protocol numbers ; Ethernet protocol numbers
ETHER_ARP equ 0x0608 ETHER_ARP equ 0x0608
ETHER_IPv4 equ 0x0008 ; Reversed from 0800 for intel ETHER_IPv4 equ 0x0008
ETHER_PPP_DISCOVERY equ 0x6388 ETHER_PPP_DISCOVERY equ 0x6388
ETHER_PPP_SESSION equ 0x6488 ETHER_PPP_SESSION equ 0x6488
@ -52,7 +51,6 @@ AF_INET4 equ 2
;AF_AAL5 equ 8 ;AF_AAL5 equ 8
;AF_X25 equ 9 ;AF_X25 equ 9
AF_INET6 equ 10 AF_INET6 equ 10
;AF_MAX equ 12
; Internet protocol numbers ; Internet protocol numbers
IP_PROTO_IP equ 0 IP_PROTO_IP equ 0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,409 @@
;-----------------------------------------------------------------
;
; 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

View File

@ -0,0 +1,365 @@
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
;---------------------
; 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
}
;---------------------------
;
; 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
;-------------------------
;
; TCP_drop
;
; IN: eax = socket ptr
; ebx = error number
;
; OUT: eax = socket ptr
;
;-------------------------
align 4
TCP_drop:
DEBUGF 1,"TCP_drop\n"
cmp [eax + TCP_SOCKET.t_state], TCB_SYN_RECEIVED
jl .no_syn_received
mov [eax + TCP_SOCKET.t_state], TCB_CLOSED
call TCP_output
;;; 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_close
;
; IN: eax = socket ptr
; OUT: eax = socket ptr
;
;-------------------------
align 4
TCP_close:
DEBUGF 1,"TCP_close\n"
;;; TODO: update RTT and mean deviation
;;; TODO: update slow start threshold
;;; TODO: release connection resources
; Now, mark the socket as being disconnected
mov [eax + SOCKET.state], 0 ;;; FIXME
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 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
;---------------------------------------
;
; The easy way to send an ACK/RST/keepalive segment
;
; TCP_respond_socket:
;
; IN: ebx = socket ptr
; cl = flags
;
;--------------------------------------
align 4
TCP_respond_socket:
DEBUGF 1,"TCP_respond_socket\n"
;---------------------
; Create the IP packet
push cx ebx
mov eax, [ebx + IP_SOCKET.RemoteIP]
mov ebx, [ebx + IP_SOCKET.LocalIP]
mov ecx, TCP_segment.Data
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]
rol ax, 8
stosw
mov ax, [esi + TCP_SOCKET.RemotePort]
rol ax, 8
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
stosb
mov al, cl
stosb
mov ax, [esi + TCP_SOCKET.RCV_WND]
rol ax, 8
stosw ; window
xor eax, eax
stosd ; checksum + urgentpointer
;---------------------
; Fill in the checksum
.checksum:
sub edi, TCP_segment.Data
mov ecx, TCP_segment.Data
xchg esi, edi
TCP_checksum (edi + IP_SOCKET.LocalIP), (esi + IP_SOCKET.RemoteIP)
mov [esi+TCP_segment.Checksum], dx
;--------------------
; And send the segment
call [ebx + NET_DEVICE.transmit]
ret
.error:
DEBUGF 1,"TCP_respond failed\n"
add esp, 2+4
ret
;-------------------------
; TCP_respond.segment:
;
; IN: edx = segment ptr (a previously received segment)
; cl = flags
align 4
TCP_respond_segment:
DEBUGF 1,"TCP_respond_segment\n"
;---------------------
; Create the IP packet
push cx edx
mov ebx, [edx - 20 + IPv4_Packet.SourceAddress] ;;;; and what if ip packet had options?!
mov eax, [edx - 20 + IPv4_Packet.DestinationAddress] ;;;
mov ecx, TCP_segment.Data
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_segment.DestinationPort]
rol ax, 8
stosw
mov ax, [esi + TCP_segment.SourcePort]
rol ax, 8
stosw
mov eax, [esi + TCP_segment.AckNumber]
bswap eax
stosd
xor eax, eax
stosd
mov al, 0x50 ; Dataoffset: 20 bytes
stosb
mov al, cl
stosb
mov ax, 1280
rol ax, 8
stosw ; window
xor eax, eax
stosd ; checksum + urgentpointer
;---------------------
; Fill in the checksum
.checksum:
lea esi, [edi - TCP_segment.Data]
mov ecx, TCP_segment.Data
TCP_checksum (esi - 20 + IPv4_Packet.DestinationAddress), (esi - 20 + IPv4_Packet.DestinationAddress)
mov [esi+TCP_segment.Checksum], dx
;--------------------
; And send the segment
call [ebx + NET_DEVICE.transmit]
ret
.error:
DEBUGF 1,"TCP_respond failed\n"
add esp, 2+4
ret

View File

@ -0,0 +1,108 @@
;----------------------
; 160 ms timer
;----------------------
macro TCP_timer_160ms {
local .loop
local .exit
mov eax, net_sockets
.loop:
mov eax, [eax + SOCKET.NextPtr]
or eax, eax
jz .exit
cmp [eax + SOCKET.Protocol], IP_PROTO_TCP ;;; We should also check if family is AF_INET
jne .loop
dec [eax + TCP_SOCKET.timer_ack]
jnz .loop
DEBUGF 1,"TCP ack for socket %x expired, time to piggyback!\n", eax
push eax
call TCP_respond_socket
pop eax
jmp .loop
.exit:
}
;----------------------
; 640 ms timer
;----------------------
macro TCP_timer_640ms {
local .loop
local .exit
; Update TCP sequence number
add [TCP_sequence_num], 64000
; scan through all the active TCP sockets, decrementing ALL timers
; timers do not have the chance to wrap because the keepalive timer will kill the socket when it expires
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
;---------------
cmp [eax + SOCKET.lock], 0
jz @f
DEBUGF 1,"\nlocked\n"
@@:
;-----------
inc [eax + TCP_SOCKET.t_idle]
dec [eax + TCP_SOCKET.timer_retransmission]
jnz .check_more2
DEBUGF 1,"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
DEBUGF 1,"socket %x: Keepalive expired\n", eax
call TCP_close
jmp .loop
.check_more3:
dec [eax + TCP_SOCKET.timer_timed_wait]
jnz .check_more5
DEBUGF 1,"socket %x: 2MSL timer expired\n", eax
.check_more5:
dec [eax + TCP_SOCKET.timer_persist]
jnz .loop
DEBUGF 1,"socket %x: persist timer expired\n", eax
jmp .loop
.exit:
}