summaryrefslogtreecommitdiffstats
path: root/src/net/tcp.c
diff options
context:
space:
mode:
authorMichael Brown2006-12-28 00:09:46 +0100
committerMichael Brown2006-12-28 00:09:46 +0100
commit61ed298bc7dc60c84fea456444e853a73de0c901 (patch)
tree99978dfeb667df0f79356cc4132f99922e2fd803 /src/net/tcp.c
parentKeep running the main processing loop while waiting for input. (diff)
downloadipxe-61ed298bc7dc60c84fea456444e853a73de0c901.tar.gz
ipxe-61ed298bc7dc60c84fea456444e853a73de0c901.tar.xz
ipxe-61ed298bc7dc60c84fea456444e853a73de0c901.zip
Merge changes from mcb-tcp-fixes branch.
Diffstat (limited to 'src/net/tcp.c')
-rw-r--r--src/net/tcp.c1496
1 files changed, 669 insertions, 827 deletions
diff --git a/src/net/tcp.c b/src/net/tcp.c
index 2a188287..9cb2cf7e 100644
--- a/src/net/tcp.c
+++ b/src/net/tcp.c
@@ -1,1003 +1,847 @@
#include <string.h>
#include <stdlib.h>
#include <assert.h>
-#include <byteswap.h>
-#include <latch.h>
#include <errno.h>
-#include <gpxe/process.h>
-#include <gpxe/init.h>
-#include <gpxe/netdevice.h>
+#include <byteswap.h>
+#include <timer.h>
+#include <vsprintf.h>
#include <gpxe/pkbuff.h>
-#include <gpxe/ip.h>
-#include <gpxe/tcp.h>
-#include <gpxe/tcpip.h>
#include <gpxe/retry.h>
-#include "uip/uip.h"
+#include <gpxe/tcpip.h>
+#include <gpxe/tcp.h>
/** @file
*
* TCP protocol
*
- * The gPXE TCP stack is currently implemented on top of the uIP
- * protocol stack. This file provides wrappers around uIP so that
- * higher-level protocol implementations do not need to talk directly
- * to uIP (which has a somewhat baroque API).
- *
- * Basic operation is to create a #tcp_connection structure, call
- * tcp_connect() and then call run_tcpip() in a loop until the
- * operation has completed. The TCP stack will call the various
- * methods defined in the #tcp_operations structure in order to send
- * and receive data.
- *
- * See hello.c for a trivial example of a TCP protocol using this
- * API.
- *
*/
-#if USE_UIP
+static void tcp_expired ( struct retry_timer *timer, int over );
/**
- * TCP transmit buffer
- *
- * When a tcp_operations::senddata() method is called, it is
- * guaranteed to be able to use this buffer as temporary space for
- * constructing the data to be sent. For example, code such as
- *
- * @code
+ * A TCP connection
*
- * static void my_senddata ( struct tcp_connection *conn, void *buf,
- * size_t len ) {
- * len = snprintf ( buf, len, "FETCH %s\r\n", filename );
- * tcp_send ( conn, buf + already_sent, len - already_sent );
- * }
- *
- * @endcode
- *
- * is allowed, and is probably the best way to deal with
- * variably-sized data.
- *
- * Note that you cannot use this simple mechanism if you want to be
- * able to construct single data blocks of more than #len bytes.
+ * This data structure represents the internal state of a TCP
+ * connection. It is kept separate from @c struct @c tcp_application
+ * because the internal state is still required for some time after
+ * the application closes the connection.
*/
-static void *tcp_buffer = uip_buf + ( 40 + UIP_LLH_LEN );
+struct tcp_connection {
+ /** List of TCP connections */
+ struct list_head list;
+ /** The associated TCP application, if any */
+ struct tcp_application *app;
+
+ /** Remote socket address */
+ struct sockaddr_tcpip peer;
+ /** Local port, in network byte order */
+ uint16_t local_port;
+
+ /** Current TCP state */
+ unsigned int tcp_state;
+ /** Previous TCP state
+ *
+ * Maintained only for debug messages
+ */
+ unsigned int prev_tcp_state;
+ /** Current sequence number
+ *
+ * Equivalent to SND.UNA in RFC 793 terminology.
+ */
+ uint32_t snd_seq;
+ /** Unacknowledged sequence count
+ *
+ * Equivalent to (SND.NXT-SND.UNA) in RFC 793 terminology.
+ */
+ uint32_t snd_sent;
+ /** Send window
+ *
+ * Equivalent to SND.WND in RFC 793 terminology
+ */
+ uint32_t snd_win;
+ /** Current acknowledgement number
+ *
+ * Equivalent to RCV.NXT in RFC 793 terminology.
+ */
+ uint32_t rcv_ack;
-/** Size of #tcp_buffer */
-static size_t tcp_buflen = UIP_BUFSIZE - ( 40 + UIP_LLH_LEN );
+ /** Transmit packet buffer
+ *
+ * This buffer is allocated prior to calling the application's
+ * senddata() method, to provide temporary storage space.
+ */
+ struct pk_buff *tx_pkb;
+ /** Retransmission timer */
+ struct retry_timer timer;
+};
/**
- * Open a TCP connection
+ * List of registered TCP connections
+ */
+static LIST_HEAD ( tcp_conns );
+
+/**
+ * Name TCP state
*
- * @v conn TCP connection
- *
- * This sets up a new TCP connection to the remote host specified in
- * tcp_connection::sin.
+ * @v state TCP state
+ * @ret name Name of TCP state
*/
-void tcp_connect ( struct tcp_connection *conn ) {
- struct uip_conn *uip_conn;
- u16_t ipaddr[2];
-
- assert ( conn->sin.sin_addr.s_addr != 0 );
- assert ( conn->sin.sin_port != 0 );
- assert ( conn->tcp_op != NULL );
- assert ( sizeof ( uip_conn->appstate ) == sizeof ( conn ) );
-
- * ( ( uint32_t * ) ipaddr ) = conn->sin.sin_addr.s_addr;
- uip_conn = uip_connect ( ipaddr, conn->sin.sin_port );
-#warning "Use linked lists so that uip_connect() cannot fail"
- assert ( uip_conn != NULL );
- *( ( void ** ) uip_conn->appstate ) = conn;
+static inline __attribute__ (( always_inline )) const char *
+tcp_state ( int state ) {
+ switch ( state ) {
+ case TCP_CLOSED: return "CLOSED";
+ case TCP_LISTEN: return "LISTEN";
+ case TCP_SYN_SENT: return "SYN_SENT";
+ case TCP_SYN_RCVD: return "SYN_RCVD";
+ case TCP_ESTABLISHED: return "ESTABLISHED";
+ case TCP_FIN_WAIT_1: return "FIN_WAIT_1";
+ case TCP_FIN_WAIT_2: return "FIN_WAIT_2";
+ case TCP_CLOSING_OR_LAST_ACK: return "CLOSING/LAST_ACK";
+ case TCP_TIME_WAIT: return "TIME_WAIT";
+ case TCP_CLOSE_WAIT: return "CLOSE_WAIT";
+ default: return "INVALID";
+ }
}
/**
- * Send data via a TCP connection
- *
- * @v conn TCP connection
- * @v data Data to send
- * @v len Length of data
+ * Dump TCP state transition
*
- * Data will be automatically limited to the current TCP window size.
- *
- * If retransmission is required, the connection's
- * tcp_operations::senddata() method will be called again in order to
- * regenerate the data.
+ * @v conn TCP connection
*/
-void tcp_send ( struct tcp_connection *conn __unused,
- const void *data, size_t len ) {
+static inline __attribute__ (( always_inline )) void
+tcp_dump_state ( struct tcp_connection *conn ) {
- assert ( conn = *( ( void ** ) uip_conn->appstate ) );
-
- if ( len > tcp_buflen )
- len = tcp_buflen;
- memmove ( tcp_buffer, data, len );
-
- uip_send ( tcp_buffer, len );
+ if ( conn->tcp_state != conn->prev_tcp_state ) {
+ DBG ( "TCP %p transitioned from %s to %s\n", conn,
+ tcp_state ( conn->prev_tcp_state ),
+ tcp_state ( conn->tcp_state ) );
+ }
+ conn->prev_tcp_state = conn->tcp_state;
}
/**
- * Close a TCP connection
+ * Dump TCP flags
*
- * @v conn TCP connection
+ * @v flags TCP flags
*/
-void tcp_close ( struct tcp_connection *conn __unused ) {
- assert ( conn = *( ( void ** ) uip_conn->appstate ) );
- uip_close();
+static inline __attribute__ (( always_inline )) void
+tcp_dump_flags ( unsigned int flags ) {
+ if ( flags & TCP_RST )
+ DBG ( " RST" );
+ if ( flags & TCP_SYN )
+ DBG ( " SYN" );
+ if ( flags & TCP_PSH )
+ DBG ( " PSH" );
+ if ( flags & TCP_FIN )
+ DBG ( " FIN" );
+ if ( flags & TCP_ACK )
+ DBG ( " ACK" );
}
/**
- * uIP TCP application call interface
+ * Allocate TCP connection
+ *
+ * @ret conn TCP connection, or NULL
*
- * This is the entry point of gPXE from the point of view of the uIP
- * protocol stack. This function calls the appropriate methods from
- * the connection's @tcp_operations table in order to process received
- * data, transmit new data etc.
+ * Allocates TCP connection and adds it to the TCP connection list.
*/
-void uip_tcp_appcall ( void ) {
- struct tcp_connection *conn = *( ( void ** ) uip_conn->appstate );
- struct tcp_operations *op = conn->tcp_op;
-
- if ( op->closed ) {
- if ( uip_aborted() )
- op->closed ( conn, -ECONNABORTED );
- if ( uip_timedout() )
- op->closed ( conn, -ETIMEDOUT );
- if ( uip_closed() )
- op->closed ( conn, 0 );
- }
- if ( uip_connected() && op->connected )
- op->connected ( conn );
- if ( uip_acked() && op->acked )
- op->acked ( conn, uip_conn->len );
- if ( uip_newdata() && op->newdata )
- op->newdata ( conn, ( void * ) uip_appdata, uip_len );
- if ( ( uip_rexmit() || uip_newdata() || uip_acked() ||
- uip_connected() || uip_poll() ) && op->senddata )
- op->senddata ( conn, tcp_buffer, tcp_buflen );
-}
+static struct tcp_connection * alloc_tcp ( void ) {
+ struct tcp_connection *conn;
-/* Present here to allow everything to link. Will go into separate
- * udp.c file
- */
-void uip_udp_appcall ( void ) {
+ conn = calloc ( 1, sizeof ( *conn ) );
+ if ( conn ) {
+ DBG ( "TCP %p allocated\n", conn );
+ conn->tcp_state = conn->prev_tcp_state = TCP_CLOSED;
+ conn->snd_seq = random();
+ conn->timer.expired = tcp_expired;
+ list_add ( &conn->list, &tcp_conns );
+ }
+ return conn;
}
/**
- * Perform periodic processing of all TCP connections
+ * Free TCP connection
*
- * This allows TCP connections to retransmit data if necessary.
+ * @v conn TCP connection
+ *
+ * Removes connection from TCP connection list and frees the data
+ * structure.
*/
-static void tcp_periodic ( void ) {
- struct pk_buff *pkb;
- int i;
+static void free_tcp ( struct tcp_connection *conn ) {
- for ( i = 0 ; i < UIP_CONNS ; i++ ) {
- uip_periodic ( i );
- if ( uip_len > 0 ) {
- pkb = alloc_pkb ( uip_len + MAX_LL_HEADER_LEN);
- if ( ! pkb )
- continue;
-
- pkb_reserve ( pkb, MAX_LL_HEADER_LEN );
- pkb_put ( pkb, uip_len );
- memcpy ( pkb->data, uip_buf, uip_len );
+ assert ( conn );
+ assert ( conn->tcp_state == TCP_CLOSED );
+ assert ( conn->app == NULL );
- ipv4_uip_tx ( pkb );
- }
- }
+ stop_timer ( &conn->timer );
+ list_del ( &conn->list );
+ free ( conn );
+ DBG ( "TCP %p freed\n", conn );
}
/**
- * Kick a connection into life
+ * Associate TCP connection with application
*
- * @v conn TCP connection
+ * @v conn TCP connection
+ * @v app TCP application
+ */
+static void tcp_associate ( struct tcp_connection *conn,
+ struct tcp_application *app ) {
+ assert ( conn->app == NULL );
+ assert ( app->conn == NULL );
+ conn->app = app;
+ app->conn = conn;
+ DBG ( "TCP %p associated with application %p\n", conn, app );
+}
+
+/**
+ * Disassociate TCP connection from application
*
- * Call this function when you have new data to send and are not
- * already being called as part of TCP processing.
+ * @v conn TCP connection
*/
-void tcp_kick ( struct tcp_connection *conn __unused ) {
- /* Just kick all the connections; this will work for now */
- tcp_periodic();
+static void tcp_disassociate ( struct tcp_connection *conn ) {
+ struct tcp_application *app = conn->app;
+
+ if ( app ) {
+ assert ( app->conn == conn );
+ conn->app = NULL;
+ app->conn = NULL;
+ DBG ( "TCP %p disassociated from application %p\n",
+ conn, app );
+ }
}
/**
- * Single-step the TCP stack
+ * Transmit any outstanding data
*
- * @v process TCP process
+ * @v conn TCP connection
+ * @v force_send Force sending of packet
+ *
+ * Transmits any outstanding data on the connection. If the
+ * connection is in a connected state, the application's senddata()
+ * method will be called to generate the data payload, if any.
*
- * This calls tcp_periodic() at regular intervals.
+ * Note that even if an error is returned, the retransmission timer
+ * will have been started if necessary, and so the stack will
+ * eventually attempt to retransmit the failed packet.
*/
-static void tcp_step ( struct process *process ) {
- static unsigned long timeout = 0;
+static int tcp_senddata_conn ( struct tcp_connection *conn, int force_send ) {
+ struct tcp_application *app = conn->app;
+ struct pk_buff *pkb;
+ struct tcp_header *tcphdr;
+ size_t len;
+ size_t seq_len;
- if ( currticks() > timeout ) {
- timeout = currticks() + ( TICKS_PER_SEC / 10 );
- tcp_periodic ();
+ /* Allocate space to the TX buffer */
+ pkb = alloc_pkb ( MAX_PKB_LEN );
+ if ( ! pkb ) {
+ DBG ( "TCP %p could not allocate senddata buffer\n", conn );
+ /* Start the retry timer so that we attempt to
+ * retransmit this packet later. (Start it
+ * unconditionally, since without a packet buffer we
+ * can't can the senddata() callback, and so may not
+ * be able to tell whether or not we have something
+ * that actually needs to be retransmitted).
+ */
+ start_timer ( &conn->timer );
+ return -ENOMEM;
}
+ pkb_reserve ( pkb, MAX_HDR_LEN );
- schedule ( process );
-}
-
-/** TCP stack process */
-static struct process tcp_process = {
- .step = tcp_step,
-};
+ /* If we are connected, call the senddata() method, which may
+ * call tcp_send() to queue up a data payload.
+ */
+ if ( TCP_CAN_SEND_DATA ( conn->tcp_state ) &&
+ app && app->tcp_op->senddata ) {
+ conn->tx_pkb = pkb;
+ app->tcp_op->senddata ( app, pkb->data, pkb_available ( pkb ));
+ conn->tx_pkb = NULL;
+ }
-/** Initialise the TCP stack */
-static void init_tcp ( void ) {
- schedule ( &tcp_process );
-}
+ /* Calculate amount of sequence space that this transmission
+ * consumes. (SYN or FIN consume one byte, and we can never
+ * send both at once).
+ */
+ len = pkb_len ( pkb );
+ seq_len = len;
+ assert ( ! ( ( conn->tcp_state & TCP_STATE_SENDING ( TCP_SYN ) ) &&
+ ( conn->tcp_state & TCP_STATE_SENDING ( TCP_FIN ) ) ) );
+ if ( conn->tcp_state & TCP_STATE_SENDING ( TCP_SYN | TCP_FIN ) )
+ seq_len++;
+ conn->snd_sent = seq_len;
+
+ /* If we have nothing to transmit, drop the packet */
+ if ( ( seq_len == 0 ) && ! force_send ) {
+ free_pkb ( pkb );
+ return 0;
+ }
-INIT_FN ( INIT_PROCESS, init_tcp, NULL, NULL );
+ /* If we are transmitting anything that requires
+ * acknowledgement (i.e. consumes sequence space), start the
+ * retransmission timer.
+ */
+ if ( seq_len )
+ start_timer ( &conn->timer );
-#else
+ /* Fill up the TCP header */
+ tcphdr = pkb_push ( pkb, sizeof ( *tcphdr ) );
+ memset ( tcphdr, 0, sizeof ( *tcphdr ) );
+ tcphdr->src = conn->local_port;
+ tcphdr->dest = conn->peer.st_port;
+ tcphdr->seq = htonl ( conn->snd_seq );
+ tcphdr->ack = htonl ( conn->rcv_ack );
+ tcphdr->hlen = ( ( sizeof ( *tcphdr ) / 4 ) << 4 );
+ tcphdr->flags = TCP_FLAGS_SENDING ( conn->tcp_state );
+ tcphdr->win = htons ( TCP_WINDOW_SIZE );
+ tcphdr->csum = tcpip_chksum ( pkb->data, pkb_len ( pkb ) );
-/**
- * List of registered TCP connections
- */
-static LIST_HEAD ( tcp_conns );
+ /* Dump header */
+ DBG ( "TCP %p TX %d->%d %08lx..%08lx %08lx %4zd", conn,
+ ntohs ( tcphdr->src ), ntohs ( tcphdr->dest ),
+ ntohl ( tcphdr->seq ), ( ntohl ( tcphdr->seq ) + seq_len ),
+ ntohl ( tcphdr->ack ), len );
+ tcp_dump_flags ( tcphdr->flags );
+ DBG ( "\n" );
-/**
- * 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" };
+ /* Transmit packet */
+ return tcpip_tx ( pkb, &tcp_protocol, &conn->peer );
+}
/**
- * TCP state transition function
+ * Transmit any outstanding data
*
* @v conn TCP connection
- * @v nxt_state Next TCP state
+ *
+ * This function allocates space to the transmit buffer and invokes
+ * the senddata() callback function, to allow the application to
+ * transmit new data.
*/
-void tcp_set_flags ( struct tcp_connection *conn ) {
+int tcp_senddata ( struct tcp_application *app ) {
+ struct tcp_connection *conn = app->conn;
- /* 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;
- }
- if ( conn->tcp_lstate == TCP_ESTABLISHED ) {
- conn->tcp_flags |= ( TCP_FIN | TCP_ACK );
- }
- break;
- default:
- DBG ( "TCP_INVALID state %d\n", conn->tcp_state );
- return;
+ /* Check connection actually exists */
+ if ( ! conn ) {
+ DBG ( "TCP app %p has no connection\n", app );
+ return -ENOTCONN;
}
-}
-
-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;
-
- DBG ( "Transition from %s to %s\n", tcp_states[conn->tcp_lstate], tcp_states[conn->tcp_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;
- }
- tcp_set_flags ( conn );
+ return tcp_senddata_conn ( conn, 0 );
}
/**
- * Dump TCP header
- *
- * @v tcphdr TCP header
- */
-void tcp_dump ( struct tcp_header *tcphdr ) {
- DBG ( "TCP %p src:%d dest:%d seq:%lx ack:%lx hlen:%hd flags:%#hx\n",
- tcphdr, ntohs ( tcphdr->src ), ntohs ( tcphdr->dest ), ntohl ( tcphdr->seq ),
- ntohl ( tcphdr->ack ), ( ( tcphdr->hlen & TCP_MASK_HLEN ) / 16 ), ( tcphdr->flags & TCP_MASK_FLAGS ) );
-}
-
-/**
- * Initialize a TCP connection
- *
- * @v conn TCP connection
+ * Transmit data
*
- * 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.
+ * @v app TCP application
+ * @v data Data to be sent
+ * @v len Length of the data
+ * @ret rc Return status code
*
- * struct tcp_connection my_conn;
- * tcp_init_conn ( &my_conn );
- * ...
+ * This function queues data to be sent via the TCP connection. It
+ * can be called only in the context of an application's senddata()
+ * method.
*/
-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;
+int tcp_send ( struct tcp_application *app, const void *data, size_t len ) {
+ struct tcp_connection *conn = app->conn;
+ struct pk_buff *pkb;
+
+ /* Check connection actually exists */
+ if ( ! conn ) {
+ DBG ( "TCP app %p has no connection\n", app );
+ return -ENOTCONN;
+ }
+
+ /* Check that we have a packet buffer to fill */
+ pkb = conn->tx_pkb;
+ if ( ! pkb ) {
+ DBG ( "TCP app %p tried to send data outside of the "
+ "senddata() method\n", app );
+ return -EINVAL;
+ }
+
+ /* Truncate length to fit transmit window */
+ if ( len > conn->snd_win )
+ len = conn->snd_win;
+
+ /* Truncate length to fit packet buffer */
+ if ( len > pkb_available ( pkb ) )
+ len = pkb_available ( pkb );
+
+ /* Copy payload */
+ memmove ( pkb_put ( pkb, len ), data, len );
+
+ return 0;
}
-/** Retry timer
+/**
+ * Retransmission timer expired
*
* @v timer Retry timer
* @v over Failure indicator
*/
-void tcp_expired ( struct retry_timer *timer, int over ) {
+static void tcp_expired ( struct retry_timer *timer, int over ) {
struct tcp_connection *conn =
container_of ( timer, struct tcp_connection, timer );
+ struct tcp_application *app = conn->app;
+ int graceful_close = TCP_CLOSED_GRACEFULLY ( conn->tcp_state );
+
+ DBG ( "TCP %p timer %s in %s\n", conn,
+ ( over ? "expired" : "fired" ), tcp_state ( conn->tcp_state ) );
+
+ assert ( ( conn->tcp_state == TCP_SYN_SENT ) ||
+ ( conn->tcp_state == TCP_SYN_RCVD ) ||
+ ( conn->tcp_state == TCP_ESTABLISHED ) ||
+ ( conn->tcp_state == TCP_FIN_WAIT_1 ) ||
+ ( conn->tcp_state == TCP_TIME_WAIT ) ||
+ ( conn->tcp_state == TCP_CLOSE_WAIT ) ||
+ ( conn->tcp_state == TCP_CLOSING_OR_LAST_ACK ) );
+
+ /* If we have finally timed out and given up, or if this is
+ * the result of a graceful close, terminate the connection
+ */
+ if ( over || graceful_close ) {
- DBG ( "Timer expired in %s\n", tcp_states[conn->tcp_state] );
- switch ( conn->tcp_state ) {
- case TCP_SYN_SENT:
- if ( over ) {
- list_del ( &conn->list );
- tcp_trans ( conn, TCP_CLOSED );
- if ( conn->tcp_op->closed )
- conn->tcp_op->closed ( conn, -ETIMEDOUT );
- DBG ( "Timeout! Connection closed\n" );
- return;
- }
- goto send_tcp_nomsg;
- case TCP_SYN_RCVD:
- if ( over ) {
- list_del ( &conn->list );
- tcp_trans ( conn, TCP_CLOSED );
- if ( conn->tcp_op->closed )
- conn->tcp_op->closed ( conn, -ETIMEDOUT );
- goto send_tcp_nomsg;
- }
- goto send_tcp_nomsg;
- case TCP_ESTABLISHED:
- if ( conn->tcp_lstate == TCP_SYN_SENT ) {
- goto send_tcp_nomsg;
- }
- break;
- case TCP_CLOSE_WAIT:
- if ( conn->tcp_lstate == TCP_ESTABLISHED ) {
- goto send_tcp_nomsg;
- }
- break;
- case TCP_FIN_WAIT_1:
- case TCP_FIN_WAIT_2:
- goto send_tcp_nomsg;
- case TCP_CLOSING:
- case TCP_LAST_ACK:
- if ( conn->tcp_lstate == TCP_CLOSE_WAIT ) {
- goto send_tcp_nomsg;
- }
- return;
- case TCP_TIME_WAIT:
- list_del ( &conn->list );
- tcp_trans ( conn, TCP_CLOSED );
- if ( conn->tcp_op->closed )
- conn->tcp_op->closed ( conn, 0 );
- return;
- }
- /* Retransmit the data */
- tcp_set_flags ( conn );
- 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 );
- tcp_set_flags ( conn );
- int rc;
- if ( ( rc = tcp_send ( conn, TCP_NOMSG, TCP_NOMSG_LEN ) ) != 0 ) {
- DBG ( "Error sending TCP message (rc = %d)\n", rc );
- }
- return;
-}
-
-/**
- * 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_tcpip *peer ) {
- int rc;
+ /* Transition to CLOSED */
+ conn->tcp_state = TCP_CLOSED;
+ tcp_dump_state ( conn );
- /* 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;
- }
+ /* If we haven't closed gracefully, send a RST */
+ if ( ! graceful_close )
+ tcp_senddata_conn ( conn, 1 );
-#warning "Fix the port re-use bug"
- /* If we re-use the same port, the connection should be reset
- * and a new connection set up. This doesn't happen yet, so
- * force the use of a new (random) port to avoid hitting the
- * problem.
- */
- conn->local_port = 0;
+ /* Break association between application and connection */
+ tcp_disassociate ( conn );
- /* Add the connection to the set of listening connections */
- if ( ( rc = tcp_listen ( conn, conn->local_port ) ) != 0 ) {
- return rc;
- }
- memcpy ( &conn->peer, peer, sizeof ( conn->peer ) );
+ /* Free the connection */
+ free_tcp ( conn );
- /* Initialize the TCP timer */
- conn->timer.expired = tcp_expired;
-
- /* Send a SYN packet and transition to TCP_SYN_SENT */
- conn->snd_una = 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 );
-}
+ /* Notify application */
+ if ( app && app->tcp_op->closed )
+ app->tcp_op->closed ( app, -ETIMEDOUT );
-int tcp_connect ( struct tcp_connection *conn ) {
- return tcp_connectto ( conn, &conn->peer );
+ } else {
+ /* Otherwise, retransmit the packet */
+ tcp_senddata_conn ( conn, 0 );
+ }
}
/**
- * Close the connection
- *
- * @v conn
+ * Identify TCP connection by local port number
*
- * 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.
+ * @v local_port Local port (in network-endian order)
+ * @ret conn TCP connection, or NULL
*/
-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 );
- /* FIN consumes one byte on the snd stream */
-// conn->snd_una++;
- goto send_tcp_nomsg;
- case TCP_TIME_WAIT:
-#warning "Fix me"
- /* In TIME_WAIT, we should just be waiting for the
- * timer to expire, which will trigger the actual
- * closure. However, because we get confused by RST
- * packets, we end up here. This works around the
- * problem for now.
- */
- 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 );
- if ( conn->tcp_op->closed )
- conn->tcp_op->closed ( conn, 0 );
- return 0;
- case TCP_CLOSE_WAIT:
- tcp_trans ( conn, TCP_LAST_ACK );
- /* 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;
- }
+static struct tcp_connection * tcp_demux ( uint16_t local_port ) {
+ struct tcp_connection *conn;
- 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 );
+ list_for_each_entry ( conn, &tcp_conns, list ) {
+ if ( conn->local_port == local_port )
+ return conn;
+ }
+ return NULL;
}
/**
- * Bind TCP connection to local port
+ * Handle TCP received SYN
*
* @v conn TCP connection
- * @v local_port Local port, in network byte order
+ * @v seq SEQ value (in host-endian order)
* @ret rc Return status code
*/
-int tcp_bind ( struct tcp_connection *conn, uint16_t local_port ) {
- struct tcp_connection *existing;
+static int tcp_rx_syn ( struct tcp_connection *conn, uint32_t seq ) {
+
+ /* Synchronise sequence numbers on first SYN */
+ if ( ! ( conn->tcp_state & TCP_STATE_RCVD ( TCP_SYN ) ) )
+ conn->rcv_ack = seq;
+
+ /* Ignore duplicate SYN */
+ if ( ( conn->rcv_ack - seq ) > 0 )
+ return 0;
+
+ /* Mark SYN as received and start sending ACKs with each packet */
+ conn->tcp_state |= ( TCP_STATE_SENDING ( TCP_ACK ) |
+ TCP_STATE_RCVD ( TCP_SYN ) );
+
+ /* Acknowledge SYN */
+ conn->rcv_ack++;
- list_for_each_entry ( existing, &tcp_conns, list ) {
- if ( existing->local_port == local_port )
- return -EADDRINUSE;
- }
- conn->local_port = local_port;
return 0;
}
-
/**
- * Listen for a packet
+ * Handle TCP received ACK
*
* @v conn TCP connection
- * @v local_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 an
- * available port between MIN_TCP_PORT and 65535.
+ * @v ack ACK value (in host-endian order)
+ * @v win WIN value (in host-endian order)
+ * @ret rc Return status code
*/
-int tcp_listen ( struct tcp_connection *conn, uint16_t local_port ) {
- static uint16_t try_port = 1024;
- int rc;
+static int tcp_rx_ack ( struct tcp_connection *conn, uint32_t ack,
+ uint32_t win ) {
+ struct tcp_application *app = conn->app;
+ size_t ack_len = ( ack - conn->snd_seq );
+ size_t len;
+ unsigned int acked_flags = 0;
+
+ /* Ignore duplicate or out-of-range ACK */
+ if ( ack_len > conn->snd_sent ) {
+ DBG ( "TCP %p received ACK for [%08lx,%08lx), sent only "
+ "[%08lx,%08lx)\n", conn, conn->snd_seq,
+ ( conn->snd_seq + ack_len ), conn->snd_seq,
+ ( conn->snd_seq + conn->snd_sent ) );
+ return -EINVAL;
+ }
-#warning "Fix the port re-use bug"
- /* If we re-use the same port, the connection should be reset
- * and a new connection set up. This doesn't happen yet, so
- * randomise the port to avoid hitting the problem.
+ /* If we are sending flags and this ACK acknowledges all
+ * outstanding sequence points, then it acknowledges the
+ * flags. (This works since both SYN and FIN will always be
+ * the last outstanding sequence point.)
*/
- try_port = random();
-
- /* If no port specified, find the first available port */
- if ( ! local_port ) {
- for ( ; try_port ; try_port++ ) {
- if ( try_port < 1024 )
- continue;
- if ( tcp_listen ( conn, htons ( try_port ) ) == 0 )
- return 0;
- }
- return -EADDRINUSE;
+ len = ack_len;
+ if ( ack_len == conn->snd_sent ) {
+ acked_flags = ( TCP_FLAGS_SENDING ( conn->tcp_state ) &
+ ( TCP_SYN | TCP_FIN ) );
+ if ( acked_flags )
+ len--;
}
- /* Attempt bind to local port */
- if ( ( rc = tcp_bind ( conn, local_port ) ) != 0 )
- return rc;
+ /* Update SEQ and sent counters, and window size */
+ conn->snd_seq = ack;
+ conn->snd_sent = 0;
+ conn->snd_win = win;
- /* Add to TCP connection list */
- list_add ( &conn->list, &tcp_conns );
- DBG ( "TCP opened %p on port %d\n", conn, ntohs ( local_port ) );
+ /* Stop the retransmission timer */
+ stop_timer ( &conn->timer );
+
+ /* Notify application of acknowledged data, if any */
+ if ( len && app && app->tcp_op->acked )
+ app->tcp_op->acked ( app, len );
+
+ /* Mark SYN/FIN as acknowledged if applicable. */
+ if ( acked_flags ) {
+ conn->tcp_state &= ~TCP_STATE_SENDING ( TCP_SYN | TCP_FIN );
+ conn->tcp_state |= TCP_STATE_ACKED ( acked_flags );
+ }
+
+ /* Notify application of established connection, if applicable */
+ if ( ( acked_flags & TCP_SYN ) && app && app->tcp_op->connected )
+ app->tcp_op->connected ( app );
return 0;
}
/**
- * Send data
+ * Handle TCP received 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.
+ * @v conn TCP connection
+ * @v seq SEQ value (in host-endian order)
+ * @v data Data buffer
+ * @v len Length of data buffer
+ * @ret rc Return status code
*/
-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 = 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;
- }
+static int tcp_rx_data ( struct tcp_connection *conn, uint32_t seq,
+ void *data, size_t len ) {
+ struct tcp_application *app = conn->app;
+ size_t already_rcvd;
+
+ /* Ignore duplicate data */
+ already_rcvd = ( conn->rcv_ack - seq );
+ if ( already_rcvd >= len )
+ return 0;
+ data += already_rcvd;
+ len -= already_rcvd;
+
+ /* Acknowledge new data */
+ conn->rcv_ack += len;
+
+ /* Notify application */
+ if ( app && app->tcp_op->newdata )
+ app->tcp_op->newdata ( app, data, len );
- /* 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 */
- if ( conn->tcp_op->senddata )
- conn->tcp_op->senddata ( conn, conn->tx_pkb->data,
- pkb_available ( conn->tx_pkb ) );
- /* Send pure ACK if senddata() didn't call tcp_send() */
- if ( conn->tx_pkb ) {
- tcp_send ( conn, TCP_NOMSG, TCP_NOMSG_LEN );
- }
return 0;
}
-/**
- * Transmit data
+/** Handle TCP received FIN
*
- * @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
+ * @v conn TCP connection
+ * @v seq SEQ value (in host-endian order)
+ * @ret rc Return status code
*/
-int tcp_send ( struct tcp_connection *conn, const void *data, size_t len ) {
- struct sockaddr_tcpip *peer = &conn->peer;
- struct pk_buff *pkb;
- int slen;
+static int tcp_rx_fin ( struct tcp_connection *conn, uint32_t seq ) {
+ struct tcp_application *app = conn->app;
- /* Take ownership of the TX buffer from the connection */
- pkb = conn->tx_pkb;
- conn->tx_pkb = NULL;
+ /* Ignore duplicate FIN */
+ if ( ( conn->rcv_ack - seq ) > 0 )
+ return 0;
- /* 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 );
+ /* Mark FIN as received and send our own FIN */
+ conn->tcp_state |= ( TCP_STATE_RCVD ( TCP_FIN ) |
+ TCP_STATE_SENDING ( TCP_FIN ) );
- /* Fill up the TCP header */
- struct tcp_header *tcphdr = pkb_push ( pkb, sizeof ( *tcphdr ) );
+ /* Acknowledge FIN */
+ conn->rcv_ack++;
- /* 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 */
- tcphdr->dest = peer->st_port;
- 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 host byte order */
- tcphdr->csum = 0;
- tcphdr->csum = tcpip_chksum ( pkb->data, pkb_len ( pkb ) );
-
- /* Dump the TCP header */
- tcp_dump ( tcphdr );
-
- /* Start the timer */
- if ( ( conn->tcp_state == TCP_ESTABLISHED && conn->tcp_lstate == TCP_SYN_SENT ) ||
- ( conn->tcp_state == TCP_LISTEN && conn->tcp_lstate == TCP_SYN_RCVD ) ||
- ( conn->tcp_state == TCP_CLOSED && conn->tcp_lstate == TCP_SYN_RCVD ) ||
- ( conn->tcp_state == TCP_ESTABLISHED && ( len == 0 ) ) ) {
- // Don't start the timer
- } else {
- start_timer ( &conn->timer );
- }
+ /* Break association with application */
+ tcp_disassociate ( conn );
- /* Transmit packet */
- return tcpip_tx ( pkb, &tcp_protocol, peer );
+ /* Notify application */
+ if ( app && app->tcp_op->closed )
+ app->tcp_op->closed ( app, 0 );
+
+ return 0;
}
/**
* Process received packet
*
- * @v pkb Packet buffer
- * @v partial Partial checksum
+ * @v pkb Packet buffer
+ * @v partial Partial checksum
*/
static int tcp_rx ( struct pk_buff *pkb,
struct sockaddr_tcpip *st_src __unused,
struct sockaddr_tcpip *st_dest __unused ) {
- struct tcp_connection *conn;
struct tcp_header *tcphdr;
- int32_t acked, toack;
+ struct tcp_connection *conn;
unsigned int hlen;
- int rc;
-
- /* Sanity check */
+ uint32_t start_seq;
+ uint32_t seq;
+ uint32_t ack;
+ uint32_t win;
+ unsigned int flags;
+ void *data;
+ size_t len;
+ int rc = 0;
+
+ /* Sanity check packet and strip TCP header */
if ( pkb_len ( pkb ) < sizeof ( *tcphdr ) ) {
- DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) );
+ DBG ( "TCP packet too short at %d bytes (min %d bytes)\n",
+ pkb_len ( pkb ), sizeof ( *tcphdr ) );
rc = -EINVAL;
- goto done;
+ goto err;
}
-
- /* Process TCP header */
tcphdr = pkb->data;
- tcp_dump ( tcphdr );
-
- /* Verify header length */
hlen = ( ( tcphdr->hlen & TCP_MASK_HLEN ) / 16 ) * 4;
if ( hlen < sizeof ( *tcphdr ) ) {
- DBG ( "Bad header length (%d bytes)\n", hlen );
+ DBG ( "TCP header too short at %d bytes (min %d bytes)\n",
+ hlen, sizeof ( *tcphdr ) );
rc = -EINVAL;
- goto done;
+ goto err;
}
- /* TODO: Parse TCP options */
- if ( hlen != sizeof ( *tcphdr ) ) {
- DBG ( "Ignoring TCP options\n" );
+ if ( hlen > pkb_len ( pkb ) ) {
+ DBG ( "TCP header too long at %d bytes (max %d bytes)\n",
+ hlen, pkb_len ( pkb ) );
+ rc = -EINVAL;
+ goto err;
}
/* TODO: Verify checksum */
+#warning "Verify checksum"
- /* Demux TCP connection */
- list_for_each_entry ( conn, &tcp_conns, list ) {
- if ( tcphdr->dest == conn->local_port ) {
- goto found_conn;
- }
+ /* Parse parameters from header and strip header */
+ conn = tcp_demux ( tcphdr->dest );
+ start_seq = seq = ntohl ( tcphdr->seq );
+ ack = ntohl ( tcphdr->ack );
+ win = ntohs ( tcphdr->win );
+ flags = tcphdr->flags;
+ data = pkb_pull ( pkb, hlen );
+ len = pkb_len ( pkb );
+
+ /* Dump header */
+ DBG ( "TCP %p RX %d<-%d %08lx %08lx..%08lx %4zd", conn,
+ ntohs ( tcphdr->dest ), ntohs ( tcphdr->src ),
+ ntohl ( tcphdr->ack ), ntohl ( tcphdr->seq ),
+ ( ntohl ( tcphdr->seq ) + len +
+ ( ( tcphdr->flags & ( TCP_SYN | TCP_FIN ) ) ? 1 : 0 ) ), len );
+ tcp_dump_flags ( tcphdr->flags );
+ DBG ( "\n" );
+
+ /* If no connection was found, create dummy connection for
+ * sending RST
+ */
+#warning "Handle non-matched connections"
+ if ( ! conn )
+ goto err;
+
+ /* Handle RST, if present */
+#warning "Handle RST"
+ if ( flags & TCP_RST )
+ goto err;
+
+ /* Handle ACK, if present */
+ if ( flags & TCP_ACK )
+ tcp_rx_ack ( conn, ack, win );
+
+ /* Handle SYN, if present */
+ if ( flags & TCP_SYN ) {
+ tcp_rx_syn ( conn, seq );
+ seq++;
}
-
- DBG ( "No connection found on port %d\n", ntohs ( tcphdr->dest ) );
- rc = 0;
- goto done;
- found_conn:
- /* Stop the timer */
- stop_timer ( &conn->timer );
+ /* Handle new data, if any */
+ tcp_rx_data ( conn, seq, data, len );
+ seq += len;
- /* Set the advertised window */
- conn->snd_win = tcphdr->win;
-
- /* TCP State Machine */
- 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] );
- rc = -EINVAL;
- goto done;
- 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;
- conn->tcp_flags |= TCP_ACK;
-
- /* Set the sequence number for the snd stream */
- conn->snd_una = random();
- conn->tcp_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;
- conn->tcp_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 );
- if ( conn->tcp_op->connected )
- conn->tcp_op->connected ( conn );
- conn->tcp_flags |= TCP_ACK;
- tcp_senddata ( conn );
- rc = 0;
- goto done;
- } else {
- tcp_trans ( conn, TCP_SYN_RCVD );
- conn->tcp_flags |= TCP_SYN;
- goto send_tcp_nomsg;
- }
- }
- /* Unexpected packet */
- goto unexpected;
- case TCP_SYN_RCVD:
- if ( tcphdr->flags & TCP_RST ) {
- tcp_trans ( conn, TCP_LISTEN );
- if ( conn->tcp_op->closed )
- conn->tcp_op->closed ( conn, -ECONNRESET );
- rc = 0;
- goto done;
- }
- 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;
- if ( conn->tcp_op->connected )
- conn->tcp_op->connected ( conn );
- rc = 0;
- goto done;
- }
- /* Unexpected packet */
- goto unexpected;
- case TCP_ESTABLISHED:
- if ( tcphdr->flags & TCP_FIN ) {
- if ( tcphdr->flags & TCP_ACK ) {
- tcp_trans ( conn, TCP_LAST_ACK );
- conn->tcp_flags |= TCP_FIN;
- } else {
- tcp_trans ( conn, TCP_CLOSE_WAIT );
- }
- /* FIN consumes one byte */
- conn->rcv_nxt++;
- conn->tcp_flags |= TCP_ACK;
- /* Send the packet */
- goto send_tcp_nomsg;
- }
- /* Packet might contain data */
- break;
- case TCP_FIN_WAIT_1:
- if ( tcphdr->flags & TCP_FIN ) {
- conn->rcv_nxt++;
- conn->tcp_flags |= TCP_ACK;
- 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++;
- conn->tcp_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 );
- start_timer ( &conn->timer );
- rc = 0;
- goto done;
- }
- /* 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 ) {
- list_del ( &conn->list );
- tcp_trans ( conn, TCP_CLOSED );
- if ( conn->tcp_op->closed )
- conn->tcp_op->closed ( conn, 0 );
- rc = 0;
- goto done;
- }
- /* Unexpected packet */
- goto unexpected;
+ /* Handle FIN, if present */
+ if ( flags & TCP_FIN ) {
+ tcp_rx_fin ( conn, seq );
+ seq++;
}
- /**
- * Any packet reaching this point either contains new data or
- * acknowledges previously transmitted data.
+ /* Dump out any state change as a result of SYN, FIN or ACK */
+ tcp_dump_state ( conn );
+
+ /* Send out any pending data. If peer is expecting an ACK for
+ * this packet then force sending a reply.
*/
- assert ( ( tcphdr->flags & TCP_ACK ) ||
- pkb_len ( pkb ) > sizeof ( *tcphdr ) );
+ tcp_senddata_conn ( conn, ( start_seq != seq ) );
- /**
- * Check if the received packet ACKs sent data
+ /* If this packet was the last we expect to receive, set up
+ * timer to expire and cause the connection to be freed.
*/
- if ( tcphdr->flags & TCP_ACK ) {
- acked = ntohl ( tcphdr->ack ) - conn->snd_una;
- if ( acked < 0 ) {
- /* Packet ACKs previously ACKed data */
- DBG ( "Previously ACKed data %lx\n",
- ntohl ( tcphdr->ack ) );
- rc = 0;
- goto done;
+ if ( TCP_CLOSED_GRACEFULLY ( conn->tcp_state ) ) {
+ conn->timer.timeout = ( 2 * TCP_MSL );
+ start_timer ( &conn->timer );
+ }
+
+ err:
+ /* Free received packet */
+ free_pkb ( pkb );
+ return rc;
+}
+
+/**
+ * Bind TCP connection to local port
+ *
+ * @v conn TCP connection
+ * @v local_port Local port (in network byte order), or 0
+ * @ret rc Return status code
+ *
+ * This function adds the connection to the list of registered TCP
+ * connections. If the local port is 0, the connection is assigned an
+ * available port between 1024 and 65535.
+ */
+static int tcp_bind ( struct tcp_connection *conn, uint16_t local_port ) {
+ struct tcp_connection *existing;
+ static uint16_t try_port = 1024;
+
+#warning "Fix the port re-use bug"
+ try_port = random();
+
+ /* If no port specified, find the first available port */
+ if ( ! local_port ) {
+ for ( ; try_port ; try_port++ ) {
+ if ( try_port < 1024 )
+ continue;
+ if ( tcp_bind ( conn, htons ( try_port ) ) == 0 )
+ return 0;
}
- /* Invoke the acked() callback */
- conn->snd_una += acked;
- if ( conn->tcp_op->acked )
- conn->tcp_op->acked ( conn, acked );
+ DBG ( "TCP %p could not bind: no free ports remaining\n",
+ conn );
+ return -EADDRINUSE;
}
-
- /**
- * Check if packet contains new data
- */
- toack = pkb_len ( pkb ) - hlen;
- if ( toack >= 0 ) {
- /* Check the sequence number */
- if ( conn->rcv_nxt == ntohl ( tcphdr->seq ) ) {
- conn->rcv_nxt += toack;
- if ( conn->tcp_op->newdata )
- conn->tcp_op->newdata ( conn, pkb->data + hlen,
- toack );
- } else {
- DBG ( "Unexpected sequence number %lx (wanted %lx)\n",
- ntohl ( tcphdr->ack ), conn->rcv_nxt );
+
+ /* Attempt bind to local port */
+ list_for_each_entry ( existing, &tcp_conns, list ) {
+ if ( existing->local_port == local_port ) {
+ DBG ( "TCP %p could not bind: port %d in use\n",
+ conn, ntohs ( local_port ) );
+ return -EADDRINUSE;
}
- conn->tcp_flags |= TCP_ACK;
}
-
- /**
- * Send data
+ conn->local_port = local_port;
+
+ DBG ( "TCP %p bound to port %d\n", conn, ntohs ( local_port ) );
+ return 0;
+}
+
+/**
+ * Connect to a remote server
+ *
+ * @v app TCP application
+ * @v peer Remote socket address
+ * @v local_port Local port number (in network byte order), or 0
+ * @ret rc Return status code
+ *
+ * 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_connect ( struct tcp_application *app, struct sockaddr_tcpip *peer,
+ uint16_t local_port ) {
+ struct tcp_connection *conn;
+ int rc;
+
+ /* Application must not already have an open connection */
+ if ( app->conn ) {
+ DBG ( "TCP app %p already open on %p\n", app, app->conn );
+ return -EISCONN;
+ }
+
+ /* Allocate connection state storage and add to connection list */
+ conn = alloc_tcp();
+ if ( ! conn ) {
+ DBG ( "TCP app %p could not allocate connection\n", app );
+ return -ENOMEM;
+ }
+
+ /* Bind to peer and to local port */
+ memcpy ( &conn->peer, peer, sizeof ( conn->peer ) );
+ if ( ( rc = tcp_bind ( conn, local_port ) ) != 0 ) {
+ free_tcp ( conn );
+ return rc;
+ }
+
+ /* Associate with application */
+ tcp_associate ( conn, app );
+
+ /* Transition to TCP_SYN_SENT and send the SYN */
+ conn->tcp_state = TCP_SYN_SENT;
+ tcp_dump_state ( conn );
+ tcp_senddata_conn ( conn, 0 );
+
+ return 0;
+}
+
+/**
+ * Close the connection
+ *
+ * @v app TCP application
+ *
+ * The association between the application and the TCP connection is
+ * immediately severed, and the TCP application data structure can be
+ * reused or freed immediately. The TCP connection will persist until
+ * the state machine has returned to the TCP_CLOSED state.
+ */
+void tcp_close ( struct tcp_application *app ) {
+ struct tcp_connection *conn = app->conn;
+
+ /* If no connection exists, do nothing */
+ if ( ! conn )
+ return;
+
+ /* Break association between application and connection */
+ tcp_disassociate ( conn );
+
+ /* If we have not yet received a SYN (i.e. we are in CLOSED,
+ * LISTEN or SYN_SENT), just delete the connection
*/
- tcp_senddata ( conn );
- rc = 0;
- goto done;
-
- send_tcp_nomsg:
- free_pkb ( conn->tx_pkb );
- conn->tx_pkb = alloc_pkb ( MIN_PKB_LEN );
- pkb_reserve ( conn->tx_pkb, MAX_HDR_LEN );
- if ( ( rc = tcp_send ( conn, TCP_NOMSG, TCP_NOMSG_LEN ) ) != 0 ) {
- DBG ( "Error sending TCP message (rc = %d)\n", rc );
+ if ( ! ( conn->tcp_state & TCP_STATE_RCVD ( TCP_SYN ) ) ) {
+ conn->tcp_state = TCP_CLOSED;
+ tcp_dump_state ( conn );
+ free_tcp ( conn );
+ return;
}
- goto done;
-
- unexpected:
- DBG ( "Unexpected packet received in %s with flags = %#hx\n",
- tcp_states[conn->tcp_state], tcphdr->flags & TCP_MASK_FLAGS );
- tcp_close ( conn );
- free_pkb ( conn->tx_pkb );
- conn->tx_pkb = NULL;
- rc = -EINVAL;
- goto done;
-
- done:
- free_pkb ( pkb );
- return rc;
+
+ /* If we have sent a SYN but not had it acknowledged (i.e. we
+ * are in SYN_RCVD), pretend that it has been acknowledged so
+ * that we can send a FIN without breaking things.
+ */
+ if ( conn->tcp_state & TCP_STATE_SENDING ( TCP_SYN ) )
+ tcp_rx_ack ( conn, ( conn->snd_seq + 1 ), 0 );
+
+ /* Send a FIN to initiate the close */
+ conn->tcp_state |= TCP_STATE_SENDING ( TCP_FIN );
+ tcp_dump_state ( conn );
+ tcp_senddata_conn ( conn, 0 );
}
/** TCP protocol */
@@ -1007,5 +851,3 @@ struct tcpip_protocol tcp_protocol __tcpip_protocol = {
.tcpip_proto = IP_TCP,
.csum_offset = 16,
};
-
-#endif /* USE_UIP */