TCP support
authorNikhil Chandru Rao <nikhilcrao@users.sourceforge.net>
Tue, 1 Aug 2006 20:46:50 +0000 (20:46 +0000)
committerNikhil Chandru Rao <nikhilcrao@users.sourceforge.net>
Tue, 1 Aug 2006 20:46:50 +0000 (20:46 +0000)
src/include/gpxe/tcp.h
src/net/tcp.c

index fb0c89a..cc7d666 100644 (file)
@@ -11,6 +11,8 @@
 
 #include <stddef.h>
 #include <gpxe/in.h>
+#include <gpxe/list.h>
+#include <gpxe/pkbuff.h>
 
 struct tcp_connection;
 
@@ -87,6 +89,8 @@ struct tcp_operations {
                              size_t len );
 };
 
+#if USE_UIP
+
 /**
  * A TCP connection
  *
@@ -104,4 +108,106 @@ extern void tcp_send ( struct tcp_connection *conn, const void *data,
 extern void tcp_kick ( struct tcp_connection *conn );
 extern void tcp_close ( struct tcp_connection *conn );
 
+#else
+
+#define TCP_NOMSG ""
+#define TCP_NOMSG_LEN 0
+
+/* Smallest port number on which a TCP connection can listen */
+#define TCP_MIN_PORT 1
+
+/* Some PKB constants */
+#define MAX_HDR_LEN    100
+#define MAX_PKB_LEN    1500
+#define MIN_PKB_LEN    MAX_HDR_LEN + 100 /* To account for padding by LL */
+
+/**
+ * TCP states
+ */
+#define TCP_CLOSED     0
+#define TCP_LISTEN     1
+#define TCP_SYN_SENT   2
+#define TCP_SYN_RCVD   3
+#define TCP_ESTABLISHED        4
+#define TCP_FIN_WAIT_1 5
+#define TCP_FIN_WAIT_2 6
+#define TCP_CLOSING    7
+#define TCP_TIME_WAIT  8
+#define TCP_CLOSE_WAIT 9
+#define TCP_LAST_ACK   10
+
+#define TCP_INVALID    11
+
+/**
+ * A TCP connection
+ */
+struct tcp_connection {
+       struct sockaddr sa;             /* Remote socket address */
+       struct sockaddr_in sin;         /* Internet socket address */
+       uint16_t local_port;            /* Local port, in network byte order */
+       int tcp_state;                  /* TCP state */
+       int tcp_lstate;                 /* Last TCP state */
+       uint32_t snd_una;               /* Lowest unacked byte on snd stream */
+       uint32_t snd_win;               /* Offered by remote end */
+       uint32_t rcv_nxt;               /* Next expected byte on rcv stream */
+       uint32_t rcv_win;               /* Advertised to receiver */
+       uint8_t tcp_flags;              /* TCP header flags */
+       struct list_head list;          /* List of TCP connections */
+       struct pk_buff *tx_pkb;         /* Transmit packet buffer */
+       struct tcp_operations *tcp_op;  /* Operations table for connection */
+};
+
+/**
+ * Connection closed status codes
+ */
+#define CONN_SNDCLOSE  0
+#define CONN_RESTART   1
+#define CONN_TIMEOUT   2
+#define CONN_RCVCLOSE  3
+
+/**
+ * A TCP header
+ */
+struct tcp_header {
+       uint16_t src;           /* Source port */
+       uint16_t dest;          /* Destination port */
+       uint32_t seq;           /* Sequence number */
+       uint32_t ack;           /* Acknowledgement number */
+       uint8_t hlen;           /* Header length (4), Reserved (4) */
+       uint8_t flags;          /* Reserved (2), Flags (6) */
+       uint16_t win;           /* Advertised window */
+       uint16_t csum;          /* Checksum */
+       uint16_t urg;           /* Urgent pointer */
+};
+
+/**
+ * TCP masks
+ */
+#define TCP_MASK_HLEN  0xf0
+#define TCP_MASK_FLAGS 0x3f
+
+/**
+ * TCP flags
+ */
+#define TCP_RST                0x20
+#define TCP_ACK                0x10
+#define TCP_PSH                0x08
+#define TCP_URG                0x04
+#define TCP_SYN                0x02
+#define TCP_FIN                0x01
+
+extern struct tcpip_protocol tcp_protocol;
+
+extern void tcp_init_conn ( struct tcp_connection *conn );
+extern int tcp_connect ( struct tcp_connection *conn );
+extern int tcp_connectto ( struct tcp_connection *conn, struct sockaddr *peer );
+extern int tcp_listen ( struct tcp_connection *conn, uint16_t port );
+extern int tcp_senddata ( struct tcp_connection *conn );
+extern int tcp_close ( struct tcp_connection *conn );
+
+extern int tcp_send ( struct tcp_connection *conn, const void *data, 
+                     size_t len );
+
+#endif /* USE_UIP */
+
 #endif /* _GPXE_TCP_H */
index afb1838..1c80132 100644 (file)
@@ -1,4 +1,5 @@
 #include <string.h>
+#include <stdlib.h>
 #include <assert.h>
 #include <byteswap.h>
 #include <latch.h>
@@ -9,6 +10,7 @@
 #include <gpxe/pkbuff.h>
 #include <gpxe/ip.h>
 #include <gpxe/tcp.h>
+#include <gpxe/tcpip.h>
 #include "uip/uip.h"
 
 /** @file
@@ -31,6 +33,8 @@
  *
  */
 
+#if USE_UIP
+
 /**
  * TCP transmit buffer
  *
@@ -222,3 +226,609 @@ static void init_tcp ( void ) {
 }
 
 INIT_FN ( INIT_PROCESS, init_tcp, NULL, NULL );
+
+#else
+
+/**
+ * List of registered TCP connections
+ */
+static LIST_HEAD ( tcp_conns );
+
+/**
+ * List of TCP states
+ */
+static const char *tcp_states[] = {
+       "CLOSED",
+       "LISTEN",
+       "SYN_SENT",
+       "SYN_RCVD",
+       "ESTABLISHED",
+       "FIN_WAIT_1",
+       "FIN_WAIT_2",
+       "CLOSING",
+       "TIME_WAIT",
+       "CLOSE_WAIT",
+       "LAST_ACK",
+       "INVALID" };
+
+/**
+ * TCP state transition function
+ *
+ * @v conn     TCP connection
+ * @v nxt_state Next TCP state
+ */
+void tcp_trans ( struct tcp_connection *conn, int nxt_state ) {
+       /* Remember the last state */
+       conn->tcp_lstate = conn->tcp_state;
+       conn->tcp_state = nxt_state;
+
+       /* TODO: Check if this check is required */
+       if ( conn->tcp_lstate == conn->tcp_state || 
+            conn->tcp_state == TCP_INVALID ) {
+               conn->tcp_flags = 0;
+               return;
+       }
+
+       /* Set the TCP flags */
+       switch ( conn->tcp_state ) {
+       case TCP_CLOSED:
+               if ( conn->tcp_lstate == TCP_SYN_RCVD ) {
+                       conn->tcp_flags |= TCP_RST;
+               }
+               break;
+       case TCP_LISTEN:
+               break;
+       case TCP_SYN_SENT:
+               if ( conn->tcp_lstate == TCP_LISTEN ||
+                    conn->tcp_lstate == TCP_CLOSED ) {
+                       conn->tcp_flags |= TCP_SYN;
+               }
+               break;
+       case TCP_SYN_RCVD:
+               if ( conn->tcp_lstate == TCP_LISTEN ||
+                    conn->tcp_lstate == TCP_SYN_SENT ) {
+                       conn->tcp_flags |= ( TCP_SYN | TCP_ACK );
+               }
+               break;
+       case TCP_ESTABLISHED:
+               if ( conn->tcp_lstate == TCP_SYN_SENT ) {
+                       conn->tcp_flags |= TCP_ACK;
+               }
+               break;
+       case TCP_FIN_WAIT_1:
+               if ( conn->tcp_lstate == TCP_SYN_RCVD ||
+                    conn->tcp_lstate == TCP_ESTABLISHED ) {
+                       conn->tcp_flags |= TCP_FIN;
+               }
+               break;
+       case TCP_FIN_WAIT_2:
+               break;
+       case TCP_CLOSING:
+               if ( conn->tcp_lstate == TCP_FIN_WAIT_1 ) {
+                       conn->tcp_flags |= TCP_ACK;
+               }
+               break;
+       case TCP_TIME_WAIT:
+               if ( conn->tcp_lstate == TCP_FIN_WAIT_1 ||
+                    conn->tcp_lstate == TCP_FIN_WAIT_2 ) {
+                       conn->tcp_flags |= TCP_ACK;
+               }
+               break;
+       case TCP_CLOSE_WAIT:
+               if ( conn->tcp_lstate == TCP_ESTABLISHED ) {
+                       conn->tcp_flags |= TCP_ACK;
+               }
+               break;
+       case TCP_LAST_ACK:
+               if ( conn->tcp_lstate == TCP_CLOSE_WAIT ) {
+                       conn->tcp_flags |= TCP_FIN;
+               }
+               break;
+       default:
+               DBG ( "TCP_INVALID state %d\n", conn->tcp_state );
+               return;
+       }
+}
+
+/**
+ * Dump TCP header
+ *
+ * @v tcphdr   TCP header
+ */
+void tcp_dump ( struct tcp_header *tcphdr ) {
+       DBG ( "TCP header at %p+%d\n", tcphdr, sizeof ( *tcphdr ) );
+       DBG ( "\tSource port = %d, Destination port = %d\n",
+               ntohs ( tcphdr->src ), ntohs ( tcphdr->dest ) );
+       DBG ( "\tSequence Number = %ld, Acknowledgement Number = %ld\n",
+               ntohl ( tcphdr->seq ), ntohl ( tcphdr->ack ) );
+       DBG ( "\tHeader length (/4) = %hd, Flags [..RAPUSF]= %#x\n",
+               ( ( tcphdr->hlen & TCP_MASK_HLEN ) / 16 ),
+               ( tcphdr->flags & TCP_MASK_FLAGS ) );
+       DBG ( "\tAdvertised window = %ld, Checksum = %x, Urgent Pointer = %d\n",
+               ntohs ( tcphdr->win ), tcphdr->csum, ntohs ( tcphdr->urg ) );
+}
+
+/**
+ * Initialize a TCP connection
+ *
+ * @v conn     TCP connection
+ *
+ * This function assigns initial values to some fields in the connection
+ * structure. The application should call tcp_init_conn after creating a new
+ * connection before calling any other "tcp_*" function.
+ *
+ * struct tcp_connection my_conn;
+ * tcp_init_conn ( &my_conn );
+ * ... 
+ */
+void tcp_init_conn ( struct tcp_connection *conn ) {
+       conn->local_port = 0;
+       conn->tcp_state = TCP_CLOSED;
+       conn->tcp_lstate = TCP_INVALID;
+       conn->tx_pkb = NULL;
+       conn->tcp_op = NULL;
+}
+
+/**
+ * Connect to a remote server
+ *
+ * @v conn     TCP connection
+ * @v peer     Remote socket address
+ *
+ * This function initiates a TCP connection to the socket address specified in
+ * peer. It sends a SYN packet to peer. When the connection is established, the
+ * TCP stack calls the connected() callback function.
+ */
+int tcp_connectto ( struct tcp_connection *conn, struct sockaddr *peer ) {
+       int rc;
+
+       /* A connection can only be established from the CLOSED state */
+       if ( conn->tcp_state != TCP_CLOSED ) {
+               DBG ( "Error opening connection: Invalid state %s\n",
+                               tcp_states[conn->tcp_state] );
+               return -EISCONN;
+       }
+
+       /* Add the connection to the set of listening connections */
+       if ( ( rc = tcp_listen ( conn, conn->local_port ) ) != 0 ) {
+               return rc;
+       }
+       memcpy ( &conn->sa, peer, sizeof ( *peer ) );
+
+       /* Send a SYN packet and transition to TCP_SYN_SENT */
+       conn->snd_una = ( ( ( uint32_t ) random() ) << 16 ) & random();
+       tcp_trans ( conn, TCP_SYN_SENT );
+       /* Allocate space for the packet */
+       free_pkb ( conn->tx_pkb );
+       conn->tx_pkb = alloc_pkb ( MIN_PKB_LEN );
+       pkb_reserve ( conn->tx_pkb, MAX_HDR_LEN );
+       conn->rcv_win = MAX_PKB_LEN - MAX_HDR_LEN; /* TODO: Is this OK? */
+       return tcp_send ( conn, TCP_NOMSG, TCP_NOMSG_LEN );
+}
+
+int tcp_connect ( struct tcp_connection *conn ) {
+       return tcp_connectto ( conn, &conn->sa );
+}
+
+/**
+ * Close the connection
+ *
+ * @v conn
+ *
+ * This function sends a FIN packet to the remote end of the connection. When
+ * the remote end of the connection ACKs the FIN (FIN consumes one byte on the
+ * snd stream), the stack invokes the closed() callback function.
+ */
+int tcp_close ( struct tcp_connection *conn ) {
+       /* A connection can only be closed if it is a connected state */
+       switch ( conn->tcp_state ) {
+       case TCP_SYN_RCVD:
+       case TCP_ESTABLISHED:
+               tcp_trans ( conn, TCP_FIN_WAIT_1 );
+               conn->tcp_op->closed ( conn, CONN_SNDCLOSE ); /* TODO: Check! */
+               /* FIN consumes one byte on the snd stream */
+//             conn->snd_una++;
+               goto send_tcp_nomsg;
+       case TCP_SYN_SENT:
+       case TCP_LISTEN:
+               /**
+                * Since the connection does not expect any packets from the
+                * remote end, it can be removed from the set of listening
+                * connections.
+                */
+               list_del ( &conn->list );
+               tcp_trans ( conn, TCP_CLOSED );
+               conn->tcp_op->closed ( conn, CONN_SNDCLOSE );
+               return 0;
+       case TCP_CLOSE_WAIT:
+               tcp_trans ( conn, TCP_LAST_ACK );
+               conn->tcp_op->closed ( conn, CONN_SNDCLOSE ); /* TODO: Check! */
+               /* FIN consumes one byte on the snd stream */
+//             conn->snd_una++;
+               goto send_tcp_nomsg;
+       default:
+               DBG ( "tcp_close(): Invalid state %s\n",
+                                       tcp_states[conn->tcp_state] );
+               return -EPROTO;
+       }
+
+  send_tcp_nomsg:
+       free_pkb ( conn->tx_pkb );
+       conn->tx_pkb = alloc_pkb ( MIN_PKB_LEN );
+       conn->tcp_flags = TCP_FIN;
+       pkb_reserve ( conn->tx_pkb, MAX_HDR_LEN );
+       return tcp_send ( conn, TCP_NOMSG, TCP_NOMSG_LEN );
+}
+
+
+/**
+ * Listen for a packet
+ *
+ * @v conn     TCP connection
+ * @v port     Local port, in network byte order
+ *
+ * This function adds the connection to a list of registered tcp connections. If
+ * the local port is 0, the connection is assigned the lowest available port
+ * between MIN_TCP_PORT and 65535.
+ */
+int tcp_listen ( struct tcp_connection *conn, uint16_t port ) {
+       struct tcp_connection *cconn;
+       if ( port != 0 ) {
+               list_for_each_entry ( cconn, &tcp_conns, list ) {
+                       if ( cconn->local_port == port ) {
+                               DBG ( "Error listening to %d\n", 
+                                                       ntohs ( port ) );
+                               return -EISCONN;
+                       }
+               }
+               /* Add the connection to the list of registered connections */
+               conn->local_port = port;
+               list_add ( &conn->list, &tcp_conns );
+               return 0;
+       }
+       /* Assigning lowest port not supported */
+       DBG ( "Assigning lowest port not implemented\n");
+       return -ENOSYS;
+}
+
+/**
+ * Send data
+ *
+ * @v conn     TCP connection
+ * 
+ * This function allocates space to the transmit buffer and invokes the
+ * senddata() callback function. It passes the allocated buffer to senddata().
+ * The applicaion may use this space to write it's data.
+ */
+int tcp_senddata ( struct tcp_connection *conn ) {
+       /* The connection must be in a state in which the user can send data */
+       switch ( conn->tcp_state ) {
+       case TCP_LISTEN:
+               tcp_trans ( conn, TCP_SYN_SENT );
+               conn->snd_una = ( ( ( uint32_t ) random() ) << 16 ) & random();
+               break;
+       case TCP_ESTABLISHED:
+       case TCP_CLOSE_WAIT:
+               break;
+       default:
+               DBG ( "tcp_senddata: Invalid state %s\n",
+                               tcp_states[conn->tcp_state] );
+               return -EPROTO;
+       }
+
+       /* Allocate space to the TX buffer */
+       free_pkb ( conn->tx_pkb );
+       conn->tx_pkb = alloc_pkb ( MAX_PKB_LEN );
+       if ( !conn->tx_pkb ) {
+               DBG ( "Insufficient memory\n" );
+               return -ENOMEM;
+       }
+       pkb_reserve ( conn->tx_pkb, MAX_HDR_LEN );
+       /* Set the advertised window */
+       conn->rcv_win = pkb_available ( conn->tx_pkb );
+       /* Call the senddata() call back function */
+       conn->tcp_op->senddata ( conn, conn->tx_pkb->data, 
+                                       pkb_available ( conn->tx_pkb ) );
+       return 0;
+}
+
+/**
+ * Transmit data
+ *
+ * @v conn     TCP connection
+ * @v data     Data to be sent
+ * @v len      Length of the data
+ *
+ * This function sends data to the peer socket address
+ */
+int tcp_send ( struct tcp_connection *conn, const void *data, size_t len ) {
+       struct sockaddr *peer = &conn->sa;
+       struct pk_buff *pkb = conn->tx_pkb;
+       int slen;
+
+       /* Determine the amount of data to be sent */
+       slen = len < conn->snd_win ? len : conn->snd_win;
+       /* Copy payload */
+       memmove ( pkb_put ( pkb, slen ), data, slen );
+
+       /* Fill up the TCP header */
+       struct tcp_header *tcphdr = pkb_push ( pkb, sizeof ( *tcphdr ) );
+
+       /* Source port, assumed to be in network byte order in conn */
+       tcphdr->src = conn->local_port;
+       /* Destination port, assumed to be in network byte order in peer */
+       switch ( peer->sa_family ) {
+       case AF_INET:
+               tcphdr->dest = peer->sin.sin_port;
+               break;
+       case AF_INET6:
+               tcphdr->dest = peer->sin6.sin6_port;
+               break;
+       default:
+               DBG ( "Family type %d not supported\n", 
+                                       peer->sa_family );
+               return -EAFNOSUPPORT;
+       }
+       tcphdr->seq = htonl ( conn->snd_una );
+       tcphdr->ack = htonl ( conn->rcv_nxt );
+       /* Header length, = 0x50 (without TCP options) */
+       tcphdr->hlen = ( uint8_t ) ( ( sizeof ( *tcphdr ) / 4 ) << 4 );
+       /* Copy TCP flags, and then reset the variable */
+       tcphdr->flags = conn->tcp_flags;
+       conn->tcp_flags = 0;
+       /* Advertised window, in network byte order */
+       tcphdr->win = htons ( conn->rcv_win );
+       /* Set urgent pointer to 0 */
+       tcphdr->urg = 0;
+       /* Calculate and store partial checksum, in network byte order */
+       tcphdr->csum = 0;
+       tcphdr->csum = tcpip_chksum ( pkb->data, pkb_len ( pkb ) );
+       
+       /* Dump the TCP header */
+       tcp_dump ( tcphdr );
+
+       /* Transmit packet */
+       return tcpip_tx ( pkb, &tcp_protocol, peer );
+}
+
+/**
+ * Process received packet
+ *
+ * @v pkb      Packet buffer
+ * @v partial  Partial checksum
+ */
+void tcp_rx ( struct pk_buff *pkb, uint16_t partial ) {
+       struct tcp_connection *conn;
+       struct tcp_header *tcphdr;
+       uint32_t acked, toack;
+       int hlen;
+
+       /* Sanity check */
+       if ( pkb_len ( pkb ) < sizeof ( *tcphdr ) ) {
+               DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) );
+               return;
+       }
+
+       /* Process TCP header */
+       tcphdr = pkb->data;
+
+       /* Verify header length */
+       hlen = ( ( tcphdr->hlen & TCP_MASK_HLEN ) / 16 ) * 4;
+       if ( hlen != sizeof ( *tcphdr ) ) {
+               DBG ( "Bad header length (%d bytes)\n", hlen );
+               return;
+       }
+       
+       /* TODO: Verify checksum */
+       
+       /* Demux TCP connection */
+       list_for_each_entry ( conn, &tcp_conns, list ) {
+               if ( tcphdr->dest == conn->local_port ) {
+                       goto found_conn;
+               }
+       }
+       
+       DBG ( "No connection found on port %d\n", ntohs ( tcphdr->dest ) );
+       return;
+
+  found_conn:
+       /* Set the advertised window */
+       conn->snd_win = tcphdr->win;
+
+       /* TCP State Machine */
+       uint8_t out_flags = 0;
+       conn->tcp_lstate = conn->tcp_state;
+       switch ( conn->tcp_state ) {
+       case TCP_CLOSED:
+               DBG ( "tcp_rx(): Invalid state %s\n",
+                               tcp_states[conn->tcp_state] );
+               return;
+       case TCP_LISTEN:
+               if ( tcphdr->flags & TCP_SYN ) {
+                       tcp_trans ( conn, TCP_SYN_RCVD );
+                       /* Synchronize the sequence numbers */
+                       conn->rcv_nxt = ntohl ( tcphdr->seq ) + 1;
+                       out_flags |= TCP_ACK;
+
+                       /* Set the sequence number for the snd stream */
+                       conn->snd_una = ( ( ( uint32_t ) random() ) << 16 );
+                       conn->snd_una &= random();
+                       out_flags |= TCP_SYN;
+
+                       /* Send a SYN,ACK packet */
+                       goto send_tcp_nomsg;
+               }
+               /* Unexpected packet */
+               goto unexpected;
+       case TCP_SYN_SENT:
+               if ( tcphdr->flags & TCP_SYN ) {
+                       /* Synchronize the sequence number in rcv stream */
+                       conn->rcv_nxt = ntohl ( tcphdr->seq ) + 1;
+                       out_flags |= TCP_ACK;
+
+                       if ( tcphdr->flags & TCP_ACK ) {
+                               tcp_trans ( conn, TCP_ESTABLISHED );
+                               /**
+                                * Process ACK of SYN. This does not invoke the
+                                * acked() callback function.
+                                */
+                               conn->snd_una = ntohl ( tcphdr->ack );
+                               conn->tcp_op->connected ( conn );
+                       } else {
+                               tcp_trans ( conn, TCP_SYN_RCVD );
+                               out_flags |= TCP_SYN;
+                       }
+                       /* Send SYN,ACK or ACK packet */
+                       goto send_tcp_nomsg;
+               }
+               /* Unexpected packet */
+               goto unexpected;
+       case TCP_SYN_RCVD:
+               if ( tcphdr->flags & TCP_RST ) {
+                       tcp_trans ( conn, TCP_LISTEN );
+                       conn->tcp_op->closed ( conn, CONN_RESTART );
+                       return;
+               }
+               if ( tcphdr->flags & TCP_ACK ) {
+                       tcp_trans ( conn, TCP_ESTABLISHED );
+                       /**
+                        * Process ACK of SYN. It neither invokes the callback
+                        * function nor does it send an ACK.
+                        */
+                       conn->snd_una = tcphdr->ack - 1;
+                       conn->tcp_op->connected ( conn );
+                       return;
+               }
+               /* Unexpected packet */
+               goto unexpected;
+       case TCP_ESTABLISHED:
+               if ( tcphdr->flags & TCP_FIN ) {
+                       tcp_trans ( conn, TCP_CLOSE_WAIT );
+                       /* FIN consumes one byte */
+                       conn->rcv_nxt++;
+                       out_flags |= TCP_ACK;
+                       /* Send an acknowledgement */
+                       goto send_tcp_nomsg;
+               }
+               /* Packet might contain data */
+               break;
+       case TCP_FIN_WAIT_1:
+               if ( tcphdr->flags & TCP_FIN ) {
+                       conn->rcv_nxt++;
+                       out_flags |= TCP_ACK;
+                       conn->tcp_op->closed ( conn, CONN_SNDCLOSE );
+
+                       if ( tcphdr->flags & TCP_ACK ) {
+                               tcp_trans ( conn, TCP_TIME_WAIT );
+                       } else {
+                               tcp_trans ( conn, TCP_CLOSING );
+                       }
+                       /* Send an acknowledgement */
+                       goto send_tcp_nomsg;
+               }
+               if ( tcphdr->flags & TCP_ACK ) {
+                       tcp_trans ( conn, TCP_FIN_WAIT_2 );
+               }
+               /* Packet might contain data */
+               break;
+       case TCP_FIN_WAIT_2:
+               if ( tcphdr->flags & TCP_FIN ) {
+                       tcp_trans ( conn, TCP_TIME_WAIT );
+                       /* FIN consumes one byte */
+                       conn->rcv_nxt++;
+                       out_flags |= TCP_ACK;
+                       goto send_tcp_nomsg;
+               }
+               /* Packet might contain data */
+               break;
+       case TCP_CLOSING:
+               if ( tcphdr->flags & TCP_ACK ) {
+                       tcp_trans ( conn, TCP_TIME_WAIT );
+                       return;
+               }
+               /* Unexpected packet */
+               goto unexpected;
+       case TCP_TIME_WAIT:
+               /* Unexpected packet */
+               goto unexpected;
+       case TCP_CLOSE_WAIT:
+               /* Packet could acknowledge data */
+               break;
+       case TCP_LAST_ACK:
+               if ( tcphdr->flags & TCP_ACK ) {
+                       tcp_trans ( conn, TCP_CLOSED );
+                       return;
+               }
+               /* Unexpected packet */
+               goto unexpected;
+       }
+
+       /**
+        * Any packet reaching this point either contains new data or
+        * acknowledges previously transmitted data.
+        */
+       assert ( ( tcphdr->flags & TCP_ACK ) ||
+                pkb_len ( pkb ) > sizeof ( *tcphdr ) );
+
+       /* Check for new data */
+       toack = pkb_len ( pkb ) - hlen;
+       if ( toack > 0 ) {
+               /* Check if expected sequence number */
+               if ( conn->rcv_nxt == ntohl ( tcphdr->seq ) ) {
+                       conn->rcv_nxt += toack;
+                       conn->tcp_op->newdata ( conn, pkb->data + sizeof ( *tcphdr ), toack );
+               }
+
+               /* Acknowledge new data */
+               out_flags |= TCP_ACK;
+               if ( !( tcphdr->flags & TCP_ACK ) ) {
+                       goto send_tcp_nomsg;
+               }
+       }
+
+       /* Process ACK */
+       if ( tcphdr->flags & TCP_ACK ) {
+               acked = ntohl ( tcphdr->ack ) - conn->snd_una;
+               if ( acked < 0 ) { /* TODO: Replace all uint32_t arith */
+                       DBG ( "Previously ACKed (%d)\n", tcphdr->ack );
+                       return;
+               }
+               /* Advance snd stream */
+               conn->snd_una += acked;
+               /* Set the ACK flag */
+               conn->tcp_flags |= TCP_ACK;
+               /* Invoke the acked() callback function */
+               conn->tcp_op->acked ( conn, acked );
+               /* Invoke the senddata() callback function */
+               tcp_senddata ( conn );
+       }
+       return;
+
+  send_tcp_nomsg:
+       free_pkb ( conn->tx_pkb );
+       conn->tx_pkb = alloc_pkb ( MIN_PKB_LEN );
+       pkb_reserve ( conn->tx_pkb, MAX_HDR_LEN );
+       int rc;
+       if ( ( rc = tcp_send ( conn, TCP_NOMSG, TCP_NOMSG_LEN ) ) != 0 ) {
+               DBG ( "Error sending TCP message (rc = %d)\n", rc );
+       }
+       return;
+
+  unexpected:
+       DBG ( "Unexpected packet received in %d state with flags = %hd\n",
+                       conn->tcp_state, tcphdr->flags & TCP_MASK_FLAGS );
+       free_pkb ( conn->tx_pkb );
+       return;
+}
+
+/** TCP protocol */
+struct tcpip_protocol tcp_protocol = {
+       .name = "TCP",
+       .rx = tcp_rx,
+       .trans_proto = IP_TCP,
+       .csum_offset = 16,
+};
+
+TCPIP_PROTOCOL ( tcp_protocol );
+
+#endif /* USE_UIP */