TCP layer

You will find the books TCP/IP Illustrated, Volume 1 (Stevens) and Volume 2 (Wright & Stevens) useful when exploring the PowerNet TCP code, especially for details of sequence and ack numbers.

TCP configuration

The configuration below can now be found in PNconfig.fth.

0 equ TCPDEBUG  \ -- n
Set this flag non-zero to generate debug messages.

#1460 equ TCPDATASIZE   \ -- n ; EGS009
Transmit buffer size of pbuf less tcp hdr(ish).

#1460 2 * equ TCPTXBUFFSIZE     \ -- n
Size of the retransmission buffer for a TCP socket. One buffer of this size is allocated from the heap when a TCP socket is created.

#1460 equ TCPWINDOWSIZE \ -- n ; EGS002
TCP window size.

#100 equ TXDELAYTIME    \ -- ms
timer for delayed transmit (mSecs), default is 100 milliseconds.

#10 equ ACKDELAYTIME    \ -- ms
timer for delayed ack (mSecs), default is 10 milliseconds.

#5000 equ TXRETRYTIME   \ -- ms
timer for transmit retries (mSecs), default is 5 seconds.

#30000 equ TCPCONNECTTIME       \ -- ms
timer for incoming connections to complete (mSecs), default is 30 seconds.

#30000 equ TCPMSLTIME   \ -- ms
timer for maximum segment lifetime (mSecs, default is 30 secs).

#7200000 equ TCPIDLETIME        \ -- ms
timer for idle disconnection (mSecs, default is 2 hours).

#12 equ TCPMAXRETRIES   \ -- n
Maximum number of transmission retries before returning SOCKET_ERROR, default is 12. If a receiving socket fails to accept new data, the total timeout will be TXRETRYTIME*TCPMAXRETRIES, the default being 60 seconds.

create MSSopt   \ -- addr
Maximum Segment Size option.

Unknown socket requests

variable NoTcpHandle    \ -- addr
Holds the handle of a socket used to handle unknown TCP requests.

: Init-NoTcp-Socket     \ -- res|SOCKET_ERROR
Initialise a socket structure to handle unknown TCP requests. SOCKET_ERROR is returned if the socket cannot be initialised.

TCP structures and equates

Most equates are not documented.

STRUCT tcp_hdr  \ -- size ; of tcp header structure
TCP header structure.

STRUCT tcpcb    \ -- size
TCP control block structure is defined in structs.fth.

: TcpState@     \ *sk -- state
Get socket state.

: .tcpstate     \ n --
Display tcp socket state. Only compiled if DIAGS? is non-zero.

: ShowTcpState  \ *sk --
Display the state for the connection

TCP structure creation and deletion

: initTcpTxBuff \ buff *cb --
Use buff to initialise or reset the TCP transmit buffer.

: -TcpTxBuff    \ *cb --
FREE the TCP transmit buffer and reset the transmit pointers.

: goCLOSED      \ *sk --
Place socket in closed state.

: (CloseSocket) \ *sk --
Prepare a socket for closure. Final closure and memory recovery is performed in the TCP idle action to avoid race hazards.

: ChkTcpTxBuff  \ *cb -- ior
If the TCPCB does not have a transmit buffer, create it. Return 0 if the buffer exists or has been created. We assume that the TCPCB is valid.

TCP header use

: GetTCPHdrLen  \ *tcp -- len
Extract length of TCP header (varies with options).

: GetTCPPktLen  \ *ip -- len ; SFP005
Extract length of TCP packet

: >TCPData      \ *tcp -- *tcpdata
Get pointer to TCP packet data.

: GetTCPDataLen \ *ip -- len ; SFP005
Extract length of TCP data packet.

: GetMSS        \ *tcp defmss -- mss ; SFP045
If the TCP header in a SYN or SYN/ACK packet contains an MSS option return it, otherwise return the default value. the output is limited to the range 0..defMSS.

: GetMSSsyn     \ *tcp -- mss ; SFP045
If the TCP header in a SYN packet contains an MSS option return it, otherwise return the default value of 536. Used by servers to read the incoming MSS in a SYN packet.

: GetMSSsyn/ack \ *tcp -- mss ; SFP045
If the TCP header in a SYN packet contains an MSS option return it, otherwise return the default value of 536. Used by clients to read the incoming MSS in a SYN/ACK packet.

TCP checksum handling

: TcpCksum      \ *tcp tcplen iprem iploc -- cksum
A generic TCP checksum function. The parameters supplied are used to form a 'pseudoheader' from which the actual checksum is generated.

: GenTcpCksum   \ *sk *tcp len --
Generate the TCP checksum using a prepared header and socket data for the IP addresses. Len is the length of the full TCP packet.

TCP window size

The words in this section are the defaults for transmitting the TCP receive window size to the remote end.

PowerNet queues input packets in the socket structure before passing them to the BSD layer. Usually, ACKs are delayed. There are three ways to implement receive window handling, selected by the equate genWinSize? in the PowerNet configuration file.

: genWindowSize \ *sk *tcp --
Given a socket *sk and a TCP header at *tcp, calculate the TCP window size and place it in the TCP header. This version is used when genWinSize?=0, and implements the previous constant window size.

: skMoreIp?     \ *sk -- flag
Return true if the socket has an input packet waiting.

: NextWinSize   \ *sk -- size
Return the TCP Window size to be used for the packet send.

: genWindowSize \ *sk *tcp --
Given a socket *sk and a TCP header at *tcp, calculate the TCP window size and place it in the TCP header. This version is used when genWinSize?=1, and implements the "bang bang" control mechanism.

TCP transmission primitives

: #unsent       \ *cb -- n
Number of bytes waiting to be sent. If no transmit buffer has been allocated, this returns 0.

: #unacked      \ *cb -- n ; SBD011
Returns the number of sent bytes awaiting acknowledgement. If no transmit buffer has been allocated, this returns 0.

: setAckDelay   \ *cb --
Set the ACK delay timer.

: checkRxWin    \ *sk --
If the receive window is set to 0 and the delayed ACK timer is not running, start the delayed ACK timer.

: checkTxUnsent \ *cb --
The TCP transmit primitive *\fo{SendTcpPkt) always clears the delayed ACK timer. If there is no more data to send, clear the delayed transmit flag and set the delayed ACK timer if if the receive window is zero.

: MakeTCPhdr    \ fl ol *sk *tcp --
Take data from the socket *sk and create a TCP header at *tcp. The TCP header checksum is set to 0.

: SendTcpPkt { *sk fl *opt ol *dat dl | *pb *tcp pl -- res }
Transmit a TCP packet directly through the IP layer.

*sk

points to the socket struct to be used

fl

the flag bits to use

*opt,ol

are the option pointer/length (if non-zero)

*dat,

dl are the data pointer/length (if non-zero)

res

= SOCKET_ERROR on failure, or no. of bytes senton success

: SendTcpOpts   \ *sk fl *opt ol -- ; SFP005
Send a packet that consists of the flags and options without data.

: SendTcpFlags  \ *sk fl -- ; SFP005
Send a packet that just consists of the flags, without options or data.

: ForceTCPReset \ *sk -- ; SFP012
Force a socket reset, and then close the socket.

: sent<txbuff   \ *cb len --
Bump the transmit buffer after len bytes sent from txbuffer.

: SendBuff>TCP  { *cb *sk | nb -- }
Send any buffered data out. No action is taken if there is nothing to send. Use this only in the TCP state machine.

: SendAck>TCP   { *cb *sk | nb -- }
Send any buffered data out with an ACK.

: skValid?      \ *sk -- flag
Returns true if the socket structure is a valid TCP socket.

: skCanSend?    \ *sk -- flag
Returns true if the socket structure is valid, the socket is in TCPS_ESTABLISHED state and has not exceeded the maximum number of retry attempts.

: sendtoTCP     { hsocket *bf len flags | bufflen *sk *cb nb -- res }
Move user data into the socket's transmit buffer, waiting in the word until all data has been transferred to the buffer. This may involve packet transmission while waiting for the buffer to empty. Because the socket may be closed during transmission, the socket must be checked before each buffer fill operation. Note that parts of this code must not be pre-empted by the scheduler.

TCP state primitives

: isTCP_Fin     \ *tcp -- T|F
Set if FIN flag bit is set.

: isTCP_Syn     \ *tcp -- T|F ; set if flag bit is set
Set if SYN flag bit is set.

: isTCP_Rst     \ *tcp -- T|F ; set if flag bit is set
Set if RST flag bit is set.

: isTCP_Psh     \ *tcp -- T|F ; set if flag bit is set
Set if PSH flag bit is set.

: isTCP_Ack     \ *tcp -- T|F ; set if flag bit is set
Set if ACK flag bit is set.

: isTCP_Urg     \ *tcp -- T|F ; set if flag bit is set
Set if URG flag bit is set.

: isTCP_SYN/ACK \ *tcp -- flag ; SFP005
Set if SYN and ACK flags are both set.

: SendNoSocket  { *pb *tcp | *ip *sk *cb -- }
Handles sending the necessary RST replies when a connection request is made for a TCP socket we don't know about. Go send an RST message. The packet buffer is freed.

: set2MSL       \ *cb --
Start the 2xMSL timer for disconnection.

: setIdle       \ *cb --
Start the disconnect timer with the IDLE period.

: setConn       \ *cb --
Start the connect timer with the TCPCONNECTTIME period.

: goSYN_REC     \ *sk --
Put the given socket into TCPS_SYN_RECEIVED state. This transition is performed by a server.

: goESTABLISHED \ *sk --
Move the socket into ESTABLISHED state.

: goLAST_ACK    \ *sk --
Move the socket into LAST_ACK state from CLOSE_WAIT.

: goTIME_WAIT   \ *sk --
Move the socket into TIME_WAIT state.

: goCLOSING     \ *sk --
Move the socket into CLOSING state from FIN_WAIT1

: goFIN_WAIT_1  \ *sk --
Move socket into FIN_WAIT_1 state from SYN_RECVD or ESTABLISHED.

: goFIN_WAIT_2  \ *sk --
Move the socket into FIN_WAIT_2 state.

: goCLOSE_WAIT  \ *sk --
Move the socket into CLOSE_WAIT state after receiving a FIN in ESTABLISHED.

: goSYN_SENT    \ *sk --
Move the socket into SYN_SENT state from CLOSED.

LISTEN connection queues

From PowerNet v4.8 onwards, listening sockets save connection attempts (receipt of SYN) on a linked list of connection items. When the application, e.g. a Telnet client, wants to use a connection, it inspects the queue, and transfers the next item on the list to a new socket in SYN_RECEIVED state. The new socket waits until the socket gets to ESTABLISHED state at which point the new connection can be used to transfer data.

Compared to the previous design, listening sockets use less RAM and, because connection attempts are stored, the design is kinder to single-threaded servers.

struct /ConnItem        \ -- len
Holds unprocessed connection information after a SYN packet has been received by a listening socket. All entries are in native order.

: .ci           \ *ci --
Display the connection queue item.

: .cq           \ hs --
Display the socket's connection queue.

: TCPConnQ?     \ *sk -- flag
Return true if there is a connection on the queue.

: #TCPConnQ     \ *sk -- n
Return the number of connections on the queue.

: ciSame?       \ *ci1 *ci2 -- flag
Return true if the two connection items are for the same connection, i.e. a retry has has occurred.

: onConnQ?      \ *ci *sk -- flag
Return true if the connection is already on the socket's connection queue.

: >TCPConnQ     \ *tcp *sk --
Add an entry to a listening socket's connection queue. On failure no action is taken and we assume that the connecting socket will retry.

: TCPConnQ>     \ *skl *ska --
Take a connection from the listening socket *skl and transfer it to the accepting socket *ska which enters TCPS_SYN_RECEIVED state.

TCP state handlers

: DoTcpClosed   { *pb *sk | *ip *tcp *cb -- }
Handle incoming packet while in CLOSED state.

: DoTcpListen   { *pb *sk | *tcp *cb -- }
Handle incoming packet while in listening state (server).

: DoTcpSynReceived      { *pb *sk | *tcp *cb -- }
Handle incoming packet while in SYN received state (server).

: DoTcpSynSent  { *pb *sk | *tcp *cb -- }
Handle incoming packet while in SYN sent state (client).

: acktxbytes    \ *cb nb -- nb' ; SBD011
Removes n bytes from the transmit buffer.

: DoTcpAcknowledge      { *tcp *cb *sk | nb -- }
Handles incoming ACK. See Wright & Stevens about ack numbers.

: DoTcpEstablished      { *pb *sk | *ip *tcp *cb tdlen -- }
Handle incoming packet while in established state.

: DoTcpLastAck  { *pb *sk | *tcp *cb -- }
Handle incoming packet while in last ACK state.

: DoTcpFinWait1 { *pb *sk | *ip *tcp *cb -- }
Handle incoming packet while in FIN_WAIT1 state.

: DoTcpFinWait2 { *pb *sk | *ip *tcp *cb -- }
Handle incoming packet while in FIN_WAIT2 state.

: DoTcpClosing  { *pb *sk | *tcp *cb -- }
Handle incoming packet while in closing state.

: DoTcpCloseWait        \ *pb *sk --
Handle incoming packet while in CLOSE_WAIT state.

: DoTcpTimeWait { *pb *sk | *tcp *cb -- }
Handle incoming packet while in timed wait state.

: IncTcpPacket  { *pb hs | *sk *cb -- }
Process an incoming packet destined for hs.

: RxTCPPacket   { *pb | *ip *tcp hs *q bf -- }
Process an incoming TCP packet.

TCP timer handling

: DoTcpRetry?   \ *cb *sk -- ; SFP021
Send data if tx retry timer expiration.

: DoTcpDelayedData?     \ *cb *sk -- ; SFP021
Send data if the transmit delay timer has expired.

: DoTcpDelayedAck?      \ *cb *sk -- ; SFP021
Send ack/data if ack delay timer expiration.

: DoTcpDisconDelay      \ *cb *sk -- ; SFP021
Handle disconnect delay timer expiration.

: DoTcpConnectTimeout   \ *cb *sk -- ; SFP021
Handles timeout for failed connections.

#5 tick-ms max equ TcpIdleMs    \ -- ms ; SFP030
Interval in miliseconds between TCP idle checks. Using this timer mechanism reduces the CPU load at the expense of transmission performance on local networks. Setting the value of TcpIdleMs to zero turns the mechanism off. The value set here in tcp.fth is only used if TcpIdleMs has not been defined in your PNconfig.fth file.

0 value TcpIdleTimer    \ -- time ; SFP030
Time for next TCPIDLE check.

: TcpIdle       { | *sk *cb -- }
Handles TCP idle time processing.

Primitives for the BSD layer

: Tcp_Open      { *sk | *cb res -- res }
Allows an application to open the TCP connection. N.B. This is a blocking function. It will not return until connected or timed out.

: Tcp_Close     { *sk | *cb res -- res }
Allows an application to close the TCP connection cleanly. This is the TCP primitive for the BSD disconnect function.

TCP initialisation

: TcpInit       \ --
Initialise the TCP layer.

Checksum test code