Fandom

lwIP Wiki

Raw/TCP

91pages on
this wiki
Add New Page
Talk0 Share

(last changed: June 16, 2011)

InitializationEdit

lwip_init() must be called before any tcp functions are called.

void tcp_tmr(void)

After lwip_init() is called, you must call tcp_tmr() every TCP_TMR_INTERVAL milliseconds (default is 250 milliseconds).

However, since some time ago (1.4.0 ?), you only have to call a single function which will handle all timers for all protocols in the stack; add this to your main loop or equivalent:

sys_check_timeouts();

Identifying "this" connectionEdit

The function tcp_arg() specifies the argument that will be passed to all the callbacks for a connection.

void tcp_arg(struct tcp_pcb * pcb, void * arg)

The "pcb" argument specifies a TCP connection control block, and the "arg" argument is a pointer to some data of your own. This argument can be used by the application for any purpose; most of the times you will use it to identify this particular instance of your application.

TCP connection setupEdit

A TCP connection is identified by a protocol control block (PCB). There are two ways to set up a connection.

Passive connection (Listen)Edit

  1. Call tcp_new to create a pcb.
  2. Optionally call tcp_arg to associate an application-specific value with the pcb.
  3. Call tcp_bind to specify the local IP address and port.
  4. Call tcp_listen or tcp_listen_with_backlog. (note: these functions will free the pcb given as an argument and return a smaller listener pcb (e.g. tpcb = tcp_listen(tpcb)))
  5. Call tcp_accept to specify the function to be called when a new connection arrives. Note that there is no possibility of a socket being accepted before specifying the callback, because this is all run on the tcpip_thread.

Active connectionEdit

  1. Call tcp_new to create a pcb.
  2. Optionally call tcp_arg to associate an application-specific value with the pcb.
  3. Optionally call tcp_bind to specify the local IP address and port.
  4. Call tcp_connect.

TCP connection functionsEdit

struct tcp_pcb * tcp_new(void)

Creates a new connection control block (PCB). The connection is initially in the "closed" state. If memory is not available for creating the new pcb, NULL is returned.

err_t tcp_bind(struct tcp_pcb * pcb, struct ip_addr * ipaddr,
                u16_t port)

Binds the pcb to a local IP address and port number. The IP address can be specified as IP_ADDR_ANY in order to bind the connection to all local IP addresses. If the port is specified as zero, the function selects an available port. The connection must be in the "closed" state.

If another connection is bound to the same port, the function will return ERR_USE, otherwise ERR_OK is returned.

struct tcp_pcb * tcp_listen(struct tcp_pcb * pcb)

The "pcb" parameter specifies a connection, which must be in the "closed" state and must have been bound to a local port with the tcp_bind() function. This functions sets up the local port to listen for incoming connections.

The tcp_listen() function returns a new connection control block, and the one passed as an argument to the function will be deallocated. The reason for this behavior is that less memory is needed for a connection that is listening, so tcp_listen() will reclaim the memory needed for the original connection and allocate a new smaller memory block for the listening connection.

After calling tcp_listen(), you must call tcp_accept(). Until you do so, incoming connections for this port will be aborted.

tcp_listen() may return NULL if no memory was available for the listening connection. If so, the memory associated with the pcb passed as an argument to tcp_listen() will not be deallocated.

struct tcp_pcb * tcp_listen_with_backlog(struct tcp_pcb * pcb, u8_t backlog)

Same as tcp_listen(), but limits the number of outstanding connections in the listen queue to the value specified by the backlog argument. To use it, your need to set TCP_LISTEN_BACKLOG=1 in your lwipopts.h.

void tcp_accept(struct tcp_pcb * pcb,
                 err_t (* accept)(void * arg, struct tcp_pcb * newpcb,
                                  err_t err))

Commands a pcb to start listening for incoming connections. You must have previously called tcp_listen(). When a new connection arrives on the local port, the specified function will be called with the pcb for the new connection.

void tcp_accepted(struct tcp_pcb * pcb)

Inform lwIP that an incoming connection has been accepted. This would usually be called from the accept callback. This allows lwIP to perform housekeeping tasks, such as allowing further incoming connections to be queued in the listen backlog. The "pcb" parameter is the listening pcb, not the new connection.

err_t tcp_connect(struct tcp_pcb * pcb, struct ip_addr * ipaddr,
                   u16_t port, err_t (* connected)(void * arg,
                                                   struct tcp_pcb * tpcb,
                                                   err_t err));

Sets up the pcb to connect to the remote host and sends the initial SYN segment which opens the connection. If the connection has not already been bound to a local port, a local port is assigned to it.

The tcp_connect() function returns immediately; it does not wait for the connection to be properly setup. Instead, it will call the function specified as the fourth argument (the "connected" argument) when the connection is established. If the connection could not be properly established, either because the other host refused the connection or because the other host didn't answer, the error handling function will be called with an the "err" argument set accordingly.

The tcp_connect() function can return ERR_MEM if no memory is available for enqueueing the SYN segment. If the SYN indeed was enqueued successfully, the tcp_connect() function returns ERR_OK.

Sending TCP dataEdit

To send data on a TCP connection:

  1. Call tcp_sent() to specify a callback function for acknowledgements.
  2. Call tcp_sndbuf() to find the maximum amount of data that can be sent.
  3. Call tcp_write() to enqueue the data.
  4. Call tcp_output() to force the data to be sent.
u16_t tcp_sndbuf(struct tcp_pcb * pcb)

Returns the number of bytes of space available in the output queue.

err_t tcp_write(struct tcp_pcb * pcb, void * dataptr, u16_t len,
                 u8_t apiflags)

Enqueues the data pointed to by the argument dataptr. The length of the data is passed as the len parameter.

The apiflags argument can have either of the following bits:

  • TCP_WRITE_FLAG_COPY indicates that lwIP should allocate new memory and copy the data into it. If not specified, no new memory should be allocated and the data should only be referenced by pointer.
  • TCP_WRITE_FLAG_MORE indicates that the push flag should not be set in the TCP segment.

The tcp_write() function will fail and return ERR_MEM if the length of the data exceeds the current send buffer size or if the length of the queue of outgoing segment is larger than the upper limit defined in lwipopts.h (TCP_SND_QUEUELEN). If the function returns ERR_MEM, the application should wait until some of the currently enqueued data has been successfully received by the other host and try again.

err_t tcp_output(struct tcp_pcb * pcb)

Forces all enqueued data to be sent now.

void tcp_sent(struct tcp_pcb * pcb,
               err_t (* sent)(void * arg, struct tcp_pcb * tpcb,
                              u16_t len))

Specifies the callback function that should be called when data has been acknowledged by the remote host. The len argument passed to the callback function gives the number of bytes that were acknowledged by the last acknowledgment.

Receiving TCP dataEdit

TCP data reception is callback based; an application-specified callback function is called when new data arrives.

The TCP protocol specifies a window that tells the sending host how much data it can send on the connection. The window size for all connections is TCP_WND which may be overridden in lwipopts.h. When the application has processed the incoming data, it must call the tcp_recved() function to indicate that TCP can increase the receive window.

void tcp_recv(struct tcp_pcb * pcb,
               err_t (* recv)(void * arg, struct tcp_pcb * tpcb,
                              struct pbuf * p, err_t err))

Sets the callback function that will be called when new data arrives. If there are no errors and the callback function returns ERR_OK, then it is responsible for freeing the pbuf. Otherwise, it must not free the pbuf so that lwIP core code can store it.

If the remote host closes the connection, the callback function will be called with a NULL pbuf to indicate that fact.

void tcp_recved(struct tcp_pcb * pcb, u16_t len)

Must be called when the application has processed the data and is prepared to receive more. The purpose is to advertise a larger window when the data has been processed. The len argument indicates the length of the processed data.

Application pollingEdit

When a connection is idle (i.e., no data is either transmitted or received), lwIP will repeatedly poll the application by calling a specified callback function. This can be used either as a watchdog timer for killing connections that have stayed idle for too long, or as a method of waiting for memory to become available. For instance, if a call to tcp_write() has failed because memory wasn't available, the application may use the polling functionality to call tcp_write() again when the connection has been idle for a while.

void tcp_poll(struct tcp_pcb * pcb,
               err_t (* poll)(void * arg, struct tcp_pcb * tpcb),
               u8_t interval)

Specifies the polling interval and the callback function that should be called to poll the application. The interval is specified in number of TCP coarse grained timer shots, which typically occurs twice a second. An interval of 10 means that the application would be polled every 5 seconds.

Closing and aborting connectionsEdit

err_t tcp_close(struct tcp_pcb * pcb)

Closes the connection. The function may return ERR_MEM if no memory was available for closing the connection. If so, the application should wait and try again either by using the acknowledgment callback or the polling functionality. If the close succeeds, the function returns ERR_OK.

The pcb is deallocated by the TCP code after a call to tcp_close().

Note that data can still be received on a closed connection until the remote host acknowledges the close.

void tcp_abort(struct tcp_pcb * pcb)

Aborts the connection by sending a RST (reset) segment to the remote host. The pcb is deallocated. This function never fails.

ATTENTION: When calling this from one of the TCP callbacks, make sure you always return ERR_ABRT (and never return ERR_ABRT otherwise or you will risk accessing deallocated memory or memory leaks!

The error handlerEdit

If a connection is aborted because of an error, or a connection attempt fails (either timeout or reset), the application is alerted of this event by the err callback. Errors that might abort a connection include a shortage of memory. The callback function to be called is set using the tcp_err() function.

void tcp_err(struct tcp_pcb * pcb,
              void (* err)(void * arg, err_t err))

The error callback function does not get the pcb passed to it as a parameter since the pcb may already have been deallocated. If you need to know anything about your application inside this function, then you will use the arg parameter, as in any other function, as explained here.

Miscellaneous TCP FunctionsEdit

Nagle AlgorithmEdit

For a short overview see http://en.wikipedia.org/wiki/Nagle%27s_algorithm

tcp_nagle_enable ( struct tcp_pcb * aPcb ); // enable the nagle algorithm

tcp_nagle_disable ( struct tcp_pcb * aPcb ); // disable the nagle algorithm

tcp_nagle_disabled ( struct tcp_pcb * aPcb ); // return true if the algorithm is not enabled

Example (for xNetCann created with NETCONN_TCP):

tcp_nagle_enable ( xNetConn->pcb.tcp );

If these macros are not defined in your version of lwIP, you should

  • upgrade to the newest version (1.4.0 at the moment of writing)
  • if you can't, use (as an emergency)
    • xNewConn->pcb.tcp->flags |= TF_NODELAY to disable
    • xNewConn->pcb.tcp->flags &= ~TF_NODELAY to enable
    • ( xNewConn->pcb.tcp->flags & TF_NODELAY ) != 0 to query for disabled

TCP KeepaliveEdit

In your lwipopts.h file you need to add

  1. define LWIP_TCP_KEEPALIVE 1

Then on each TCP socket that you need keepalive support, you need to enable it via:

/* Turn on TCP Keepalive for the given pcb */
pcb->so_options |= SOF_KEEPALIVE;

/* If you need to change the time between keep alive messages */
/* Set the time between keepalive messages in milli-seconds */
pcb->keep_intvl = 75000; /* 75 seconds */

Raw TCP Sample Sequence DiagramsEdit

Because the raw TCP implementation is intended to execute primarily via callbacks, its operation tends to be closely tied to the receipt and processing of individual messages. Hence,it is helpful to have at least a passing familiarity with the low-level TCP protocol. For those without previous lwIP experience, it is sometimes not obvious what calls to make when. The following table shows a sequence diagram of interactions between a remote client and a local lwIP server. The interactions shown are for a typical (successful) connection over a request-response protocol (such as HTTP for example).


lwIP Session Establishment (Remote client / local lwIP server)
Remote client <TCP Message> lwIP Stack Action lwIP Server Action Description
- - - <= tcp_new() Create TCP PCB
- - - <= tcp_bind() Bind port number
- - - <= tcp_listen_with_backlog() Create listening endpoint (new PCB allocated)
- - - <= tcp_accept() Set accept callback
- - - <= tcp_arg() Set callback argument [ptr to server data structure]
connect => - - - Client connect to server
- SYN => - - Remote stack sends SYN
- - (allocate new PCB) - lwIP creates "pending" session
- <= SYN/ACK - - lwIP responds with SYN/ACK
<= (connect returns) - - - Remote stack notifies client of successful connection
- ACK => - - Remote stack sends ACK to complete 3-way handshake
- - (invoke accept callback) => - lwIP notifies app of new session (with new PCB)
- - - <= tcp_accepted() Server accepts connection, decrements "pending" session count
- - - <= tcp_arg() (Server allocates new session structure), sets new callback argument
- - - <= tcp_recv() Server sets recv callback
- - - <= tcp_err() Server sets error/abort callback
- - - <= tcp_sent() Server sets sent callback
- - - <= (Server returns from accept callback with OK status)
- - (mark PCB active) - -
connection established
(data can now be sent by either side)
send => TCP data => - - Client sends request data
- - (lwIP invokes server recv callback) => - -
- - - <= tcp_write(response_data, len) Server writes response data to client
- - (lwIP enqueues TCP segment) - -
- - - <= tcp_write(response_data2, len2) Server writes some more data
- - (lwIP enqueues TCP segment) - Segment may be combined with preceding segment
- - - <= tcp_recved() Server notifies lwIP to advertise larger window
- - - <= (Server returns from recv callback with OK status) -
- <= TCP data (lwIP finds queued segments to be sent) - lwIP sends data segment(s) to client, including ACK for previously received client data

Notes: tcp_write() merely enqueues TCP data for later transmission; it does not actually start transmitting. Nevertheless, when tcp_write() is called from within a recv callback as in this example, there is no need to call tcp_output() to start transmission of sent data (indeed, tcp_output() specifically declines to do anything if it is called from within the recv callback). When you have returned from the recv callback, the stack will automatically initiate sending of any data -- and the ACK for the remote client's preceding packet will be combined with the first outgoing data segment. If tcp_write() is called through some other path (perhaps as a result of some event outside of lwIP processing), it may be necessary to call tcp_output to initiate data transmission.


lwIP Session Establishment (Local lwIP client / remote server)
Remote Server <TCP Message> lwIP Stack Action lwIP Client Action Description
- - - <= tcp_new() Create TCP PCB
- - - <= [tcp_bind()] Optionally, bind specific port number or IP address
- - - <= tcp_arg() Allocate client-specific session structure, set as callback argument
- - - <= tcp_err() Set error/abort callback (used to signal connect failure)
- - - <= tcp_recv() Set recv callback (†)
- - - <= tcp_sent() Set sent callback (†)
- - - <= tcp_connect() Connect, providing connected callback
- <= SYN <= (lwIP generates SYN) - lwIP generates SYN packet to remote server
- SYN/ACK => - - Remote server stack sends SYN/ACK
- - (lwIP invokes connected callback) => - From lwIP point of view, session is now established, final ACK in the 3-part TCP handshake will be generated on return from the callback
connection established
(data can now be sent by either side)
- - - <= tcp_write(request_data, len) Client writes request data to server
- - (lwIP enqueues TCP segment) - -
- - - <= tcp_write(request_data2, len2) Client writes some more data
- - (lwIP enqueues TCP segment) - Segment may be combined with preceding segment
- - - <= tcp_output() Client signals lwIP to actually generate outgoing packets (*)
- - - <= (Client returns from connected callback) -
- <= TCP data - - lwIP generates one or more data packets
send => TCP data => - - Server sends response data
- - (lwIP invokes client recv callback) => - See preceding table

(†) The recv and sent callbacks can be established after the connection is established (e.g. in the connected callback), if desired.

(*) Note that the call to tcp_output is not actually required if data is written by the client in the connected callback since lwIP will automatically generate an ACK after the callback returns. In other cases, it may be necessary. But see also the note to the previous table.

Notes: In case of a failure to connect, the connecting client is notified of the failure via the error callback set with tcp_err().


Session Termination (Scenario 1 - remote peer shutdown)
Remote client <TCP Message> lwIP Stack Action lwIP Server Action Description
close or shutdown(SHUT_WR) => - - - Client shuts down write end of socket
- FIN => - - Remote stack sends packet with FIN bit set
- <= ACK <= (lwIP recognizes FIN, generates immediate ACK) - lwIP PCB enters CLOSE_WAIT state
- - (lwIP invokes server recv callback with NULL pbuf argument) => - lwIP signals end-of-file to server
- - - [<= tcp_write()] Server sends final data (if any)
...
- - - <= tcp_close() Server frees private data structures, shuts down connection
- <= FIN (lwIP generates FIN) - lwIP notifies remote stack that server has closed connection (PCB enters LAST_ACK state)
- ACK => - - Client acknowledges last FIN
- - lwIP processes last ACK - PCB enters CLOSED state (and is then released)



Session Termination (Scenario 2 - local server shutdown)
Remote client <TCP Message> lwIP Stack Action lwIP Server Action Description
- - - <= tcp_close() Server shuts down connection
- <= FIN <= (lwIP generates FIN packet) - PCB enters FIN_WAIT_1 state
- ACK => (lwIP updates PCB) - PCB enters FIN_WAIT_2 state
[send] => [TCP Data] => - - Client (optionally) sends last data (*)
...
close => - - - Client shuts down socket
- FIN => - - Remote stack sends FIN packet
- <= ACK <= (lwIP recognizes FIN, generates ACK) - lwIP PCB enters TIME_WAIT
- - (lwIP invokes server recv callback with NULL pbuf argument) => - lwIP signals end-of-file to server
- - - - Server frees private data structures
...
- - (TIME_WAIT timer expires) - lwIP frees PCB

(*) Note that lwIP may invoke server recv callback after server calls tcp_close(). If you don't want to receive these, make sure to zero out the recv callback (i.e. invoke tcp_recv with a NULL pointer).

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.