summaryrefslogtreecommitdiffstats
path: root/src/net
diff options
context:
space:
mode:
Diffstat (limited to 'src/net')
-rw-r--r--src/net/tcp.c1496
-rw-r--r--src/net/tcp/ftp.c158
-rw-r--r--src/net/tcp/hello.c44
-rw-r--r--src/net/tcp/http.c48
-rw-r--r--src/net/tcp/iscsi.c87
5 files changed, 826 insertions, 1007 deletions
diff --git a/src/net/tcp.c b/src/net/tcp.c
index 2a188287a..9cb2cf7eb 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 */
diff --git a/src/net/tcp/ftp.c b/src/net/tcp/ftp.c
index 6082d97db..1bdf81353 100644
--- a/src/net/tcp/ftp.c
+++ b/src/net/tcp/ftp.c
@@ -58,49 +58,31 @@ static inline const void * ftp_string_data ( struct ftp_request *ftp,
}
/**
- * Get FTP request from control TCP connection
+ * Get FTP request from control TCP application
*
- * @v conn TCP connection
+ * @v app TCP application
* @ret ftp FTP request
*/
-static inline struct ftp_request * tcp_to_ftp ( struct tcp_connection *conn ) {
- return container_of ( conn, struct ftp_request, tcp );
+static inline struct ftp_request * tcp_to_ftp ( struct tcp_application *app ) {
+ return container_of ( app, struct ftp_request, tcp );
}
/**
- * Set overall FTP operation status
+ * Mark FTP operation as complete
*
* @v ftp FTP request
* @v rc Return status code
- *
- * Set the return status that will eventually be returned via
- * ftp_done(). If multiple errors are flagged, only the first will be
- * returned.
*/
-static void ftp_set_status ( struct ftp_request *ftp, int rc ) {
- if ( ! ftp->rc )
- ftp->rc = rc;
-}
+static void ftp_done ( struct ftp_request *ftp, int rc ) {
-/**
- * Clear overall FTP operation status
- *
- * @v ftp FTP request
- */
-static void ftp_clear_status ( struct ftp_request *ftp ) {
- ftp->rc = 0;
-}
+ DBG ( "FTP %p completed with status %d\n", ftp, rc );
-/**
- * Mark FTP operation as complete
- *
- * @v ftp FTP request
- */
-static void ftp_done ( struct ftp_request *ftp ) {
-
- DBG ( "FTP %p completed with status %d\n", ftp, ftp->rc );
+ /* Close both TCP connections */
+ tcp_close ( &ftp->tcp );
+ tcp_close ( &ftp->tcp_data );
- async_done ( &ftp->aop, ftp->rc );
+ /* Mark asynchronous operation as complete */
+ async_done ( &ftp->aop, rc );
}
/**
@@ -131,7 +113,7 @@ static void ftp_parse_value ( char **text, uint8_t *value, size_t len ) {
*
* @v ftp FTP request
*
- * This is called once we have received a complete repsonse line.
+ * This is called once we have received a complete response line.
*/
static void ftp_reply ( struct ftp_request *ftp ) {
char status_major = ftp->status_text[0];
@@ -147,21 +129,31 @@ static void ftp_reply ( struct ftp_request *ftp ) {
* fatal error.
*/
if ( ! ( ( status_major == '2' ) ||
- ( ( status_major == '3' ) && ( ftp->state == FTP_USER ) ) ) )
- goto err;
+ ( ( status_major == '3' ) && ( ftp->state == FTP_USER ) ) ) ){
+ /* Flag protocol error and close connections */
+ ftp_done ( ftp, -EPROTO );
+ }
/* Open passive connection when we get "PASV" response */
if ( ftp->state == FTP_PASV ) {
char *ptr = ftp->passive_text;
- struct sockaddr_in *sin =
- ( struct sockaddr_in * ) &ftp->tcp_data.peer;
-
- sin->sin_family = AF_INET;
- ftp_parse_value ( &ptr, ( uint8_t * ) &sin->sin_addr,
- sizeof ( sin->sin_addr ) );
- ftp_parse_value ( &ptr, ( uint8_t * ) &sin->sin_port,
- sizeof ( sin->sin_port ) );
- tcp_connect ( &ftp->tcp_data );
+ union {
+ struct sockaddr_in sin;
+ struct sockaddr_tcpip st;
+ } sa;
+ int rc;
+
+ sa.sin.sin_family = AF_INET;
+ ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_addr,
+ sizeof ( sa.sin.sin_addr ) );
+ ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_port,
+ sizeof ( sa.sin.sin_port ) );
+ if ( ( rc = tcp_connect ( &ftp->tcp_data, &sa.st, 0 ) ) != 0 ){
+ DBG ( "FTP %p could not create data connection\n",
+ ftp );
+ ftp_done ( ftp, rc );
+ return;
+ }
}
/* Move to next state */
@@ -176,26 +168,21 @@ static void ftp_reply ( struct ftp_request *ftp ) {
}
return;
-
- err:
- /* Flag protocol error and close connections */
- ftp_set_status ( ftp, -EPROTO );
- tcp_close ( &ftp->tcp );
}
/**
* Handle new data arriving on FTP control channel
*
- * @v conn TCP connection
+ * @v app TCP application
* @v data New data
* @v len Length of new data
*
* Data is collected until a complete line is received, at which point
* its information is passed to ftp_reply().
*/
-static void ftp_newdata ( struct tcp_connection *conn,
+static void ftp_newdata ( struct tcp_application *app,
void *data, size_t len ) {
- struct ftp_request *ftp = tcp_to_ftp ( conn );
+ struct ftp_request *ftp = tcp_to_ftp ( app );
char *recvbuf = ftp->recvbuf;
size_t recvsize = ftp->recvsize;
char c;
@@ -242,10 +229,10 @@ static void ftp_newdata ( struct tcp_connection *conn,
/**
* Handle acknowledgement of data sent on FTP control channel
*
- * @v conn TCP connection
+ * @v app TCP application
*/
-static void ftp_acked ( struct tcp_connection *conn, size_t len ) {
- struct ftp_request *ftp = tcp_to_ftp ( conn );
+static void ftp_acked ( struct tcp_application *app, size_t len ) {
+ struct ftp_request *ftp = tcp_to_ftp ( app );
/* Mark off ACKed portion of the currently-transmitted data */
ftp->already_sent += len;
@@ -254,13 +241,13 @@ static void ftp_acked ( struct tcp_connection *conn, size_t len ) {
/**
* Construct data to send on FTP control channel
*
- * @v conn TCP connection
+ * @v app TCP application
* @v buf Temporary data buffer
* @v len Length of temporary data buffer
*/
-static void ftp_senddata ( struct tcp_connection *conn,
+static void ftp_senddata ( struct tcp_application *app,
void *buf, size_t len ) {
- struct ftp_request *ftp = tcp_to_ftp ( conn );
+ struct ftp_request *ftp = tcp_to_ftp ( app );
const struct ftp_string *string;
/* Send the as-yet-unACKed portion of the string for the
@@ -269,31 +256,24 @@ static void ftp_senddata ( struct tcp_connection *conn,
string = &ftp_strings[ftp->state];
len = snprintf ( buf, len, string->format,
ftp_string_data ( ftp, string->data_offset ) );
- tcp_send ( conn, buf + ftp->already_sent, len - ftp->already_sent );
+ tcp_send ( app, buf + ftp->already_sent, len - ftp->already_sent );
}
/**
* Handle control channel being closed
*
- * @v conn TCP connection
+ * @v app TCP application
*
* When the control channel is closed, the data channel must also be
* closed, if it is currently open.
*/
-static void ftp_closed ( struct tcp_connection *conn, int status ) {
- struct ftp_request *ftp = tcp_to_ftp ( conn );
+static void ftp_closed ( struct tcp_application *app, int status ) {
+ struct ftp_request *ftp = tcp_to_ftp ( app );
DBG ( "FTP %p control connection closed (status %d)\n", ftp, status );
- /* Close data channel and record status */
- ftp_set_status ( ftp, status );
- tcp_close ( &ftp->tcp_data );
-
- /* Mark FTP operation as complete if we are the last
- * connection to close
- */
- if ( tcp_closed ( &ftp->tcp_data ) )
- ftp_done ( ftp );
+ /* Complete FTP operation */
+ ftp_done ( ftp, status );
}
/** FTP control channel operations */
@@ -311,20 +291,20 @@ static struct tcp_operations ftp_tcp_operations = {
*/
/**
- * Get FTP request from data TCP connection
+ * Get FTP request from data TCP application
*
- * @v conn TCP connection
+ * @v app TCP application
* @ret ftp FTP request
*/
static inline struct ftp_request *
-tcp_to_ftp_data ( struct tcp_connection *conn ) {
- return container_of ( conn, struct ftp_request, tcp_data );
+tcp_to_ftp_data ( struct tcp_application *app ) {
+ return container_of ( app, struct ftp_request, tcp_data );
}
/**
* Handle data channel being closed
*
- * @v conn TCP connection
+ * @v app TCP application
*
* When the data channel is closed, the control channel should be left
* alone; the server will send a completion message via the control
@@ -332,36 +312,28 @@ tcp_to_ftp_data ( struct tcp_connection *conn ) {
*
* If the data channel is closed due to an error, we abort the request.
*/
-static void ftp_data_closed ( struct tcp_connection *conn, int status ) {
- struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+static void ftp_data_closed ( struct tcp_application *app, int status ) {
+ struct ftp_request *ftp = tcp_to_ftp_data ( app );
DBG ( "FTP %p data connection closed (status %d)\n", ftp, status );
/* If there was an error, close control channel and record status */
- if ( status ) {
- ftp_set_status ( ftp, status );
- tcp_close ( &ftp->tcp );
- }
-
- /* Mark FTP operation as complete if we are the last
- * connection to close
- */
- if ( tcp_closed ( &ftp->tcp ) )
- ftp_done ( ftp );
+ if ( status )
+ ftp_done ( ftp, status );
}
/**
* Handle new data arriving on the FTP data channel
*
- * @v conn TCP connection
+ * @v app TCP application
* @v data New data
* @v len Length of new data
*
* Data is handed off to the callback registered in the FTP request.
*/
-static void ftp_data_newdata ( struct tcp_connection *conn,
+static void ftp_data_newdata ( struct tcp_application *app,
void *data, size_t len ) {
- struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+ struct ftp_request *ftp = tcp_to_ftp_data ( app );
ftp->callback ( data, len );
}
@@ -384,14 +356,16 @@ static struct tcp_operations ftp_data_tcp_operations = {
* @v ftp FTP request
*/
struct async_operation * ftp_get ( struct ftp_request *ftp ) {
-
+ int rc;
+
DBG ( "FTP %p fetching %s\n", ftp, ftp->filename );
ftp->tcp.tcp_op = &ftp_tcp_operations;
ftp->tcp_data.tcp_op = &ftp_data_tcp_operations;
ftp->recvbuf = ftp->status_text;
ftp->recvsize = sizeof ( ftp->status_text ) - 1;
- ftp_clear_status ( ftp );
- tcp_connect ( &ftp->tcp );
+ if ( ( rc = tcp_connect ( &ftp->tcp, &ftp->server, 0 ) ) != 0 )
+ ftp_done ( ftp, rc );
+
return &ftp->aop;
}
diff --git a/src/net/tcp/hello.c b/src/net/tcp/hello.c
index d74e52f9f..4de7e8729 100644
--- a/src/net/tcp/hello.c
+++ b/src/net/tcp/hello.c
@@ -10,9 +10,9 @@
* "Hello world" TCP protocol
*
* This file implements a trivial TCP-based protocol. It connects to
- * the server specified in hello_request::tcp and transmits a single
- * message (hello_request::message). Any data received from the
- * server will be passed to the callback function,
+ * the server specified in hello_request::server and transmits a
+ * single message (hello_request::message). Any data received from
+ * the server will be passed to the callback function,
* hello_request::callback(), and once the connection has been closed,
* the asynchronous operation associated with the request will be
* marked as complete.
@@ -26,13 +26,13 @@
* }
*
* struct hello_request hello = {
+ * .server = {
+ * ...
+ * },
* .message = "hello world!",
* .callback = my_callback,
* };
*
- * hello.sin.sin_addr.s_addr = ... server IP address ...
- * hello.sin.sin_port = ... server port ...
- *
* rc = async_wait ( say_hello ( &hello ) );
*
* @endcode
@@ -44,25 +44,25 @@
*/
static inline struct hello_request *
-tcp_to_hello ( struct tcp_connection *conn ) {
- return container_of ( conn, struct hello_request, tcp );
+tcp_to_hello ( struct tcp_application *app ) {
+ return container_of ( app, struct hello_request, tcp );
}
-static void hello_closed ( struct tcp_connection *conn, int status ) {
- struct hello_request *hello = tcp_to_hello ( conn );
+static void hello_closed ( struct tcp_application *app, int status ) {
+ struct hello_request *hello = tcp_to_hello ( app );
async_done ( &hello->aop, status );
}
-static void hello_connected ( struct tcp_connection *conn ) {
- struct hello_request *hello = tcp_to_hello ( conn );
+static void hello_connected ( struct tcp_application *app ) {
+ struct hello_request *hello = tcp_to_hello ( app );
hello->remaining = strlen ( hello->message );
hello->state = HELLO_SENDING_MESSAGE;
}
-static void hello_acked ( struct tcp_connection *conn, size_t len ) {
- struct hello_request *hello = tcp_to_hello ( conn );
+static void hello_acked ( struct tcp_application *app, size_t len ) {
+ struct hello_request *hello = tcp_to_hello ( app );
hello->message += len;
hello->remaining -= len;
@@ -84,18 +84,18 @@ static void hello_acked ( struct tcp_connection *conn, size_t len ) {
}
}
-static void hello_newdata ( struct tcp_connection *conn, void *data,
+static void hello_newdata ( struct tcp_application *app, void *data,
size_t len ) {
- struct hello_request *hello = tcp_to_hello ( conn );
+ struct hello_request *hello = tcp_to_hello ( app );
hello->callback ( data, len );
}
-static void hello_senddata ( struct tcp_connection *conn,
+static void hello_senddata ( struct tcp_application *app,
void *buf __unused, size_t len __unused ) {
- struct hello_request *hello = tcp_to_hello ( conn );
+ struct hello_request *hello = tcp_to_hello ( app );
- tcp_send ( conn, hello->message, hello->remaining );
+ tcp_send ( app, hello->message, hello->remaining );
}
static struct tcp_operations hello_tcp_operations = {
@@ -112,7 +112,11 @@ static struct tcp_operations hello_tcp_operations = {
* @v hello "Hello world" request
*/
struct async_operation * say_hello ( struct hello_request *hello ) {
+ int rc;
+
hello->tcp.tcp_op = &hello_tcp_operations;
- tcp_connect ( &hello->tcp );
+ if ( ( rc = tcp_connect ( &hello->tcp, &hello->server, 0 ) ) != 0 )
+ async_done ( &hello->aop, rc );
+
return &hello->aop;
}
diff --git a/src/net/tcp/http.c b/src/net/tcp/http.c
index 01f0aeacb..3bba8b338 100644
--- a/src/net/tcp/http.c
+++ b/src/net/tcp/http.c
@@ -39,28 +39,28 @@
*/
static inline struct http_request *
-tcp_to_http ( struct tcp_connection *conn ) {
- return container_of ( conn, struct http_request, tcp );
+tcp_to_http ( struct tcp_application *app ) {
+ return container_of ( app, struct http_request, tcp );
}
/**
* Close an HTTP connection
*
- * @v conn a TCP Connection
+ * @v app a TCP Application
* @v status connection status at close
*/
-static void http_closed ( struct tcp_connection *conn, int status ) {
- struct http_request *http = tcp_to_http ( conn );
+static void http_closed ( struct tcp_application *app, int status ) {
+ struct http_request *http = tcp_to_http ( app );
async_done ( &http->aop, status );
}
/**
* Callback after a TCP connection is established
*
- * @v conn a TCP Connection
+ * @v app a TCP Application
*/
-static void http_connected ( struct tcp_connection *conn ) {
- struct http_request *http = tcp_to_http ( conn );
+static void http_connected ( struct tcp_application *app ) {
+ struct http_request *http = tcp_to_http ( app );
http->state = HTTP_REQUEST_FILE;
}
@@ -68,11 +68,11 @@ static void http_connected ( struct tcp_connection *conn ) {
/**
* Callback for when TCP data is acknowledged
*
- * @v conn a TCP Connection
+ * @v app a TCP Application
* @v len the length of data acked
*/
-static void http_acked ( struct tcp_connection *conn, size_t len __attribute__ ((unused)) ) {
- struct http_request *http = tcp_to_http ( conn );
+static void http_acked ( struct tcp_application *app, size_t len __attribute__ ((unused)) ) {
+ struct http_request *http = tcp_to_http ( app );
// assume that the whole GET request was sent in on epacket
@@ -84,7 +84,7 @@ static void http_acked ( struct tcp_connection *conn, size_t len __attribute__ (
case HTTP_RECV_FILE:
break;
case HTTP_DONE:
- //tcp_close(conn);
+ //tcp_close(app);
break;
default:
break;
@@ -95,13 +95,13 @@ static void http_acked ( struct tcp_connection *conn, size_t len __attribute__ (
/**
* Callback when new TCP data is recieved
*
- * @v conn a TCP Connection
+ * @v app a TCP Application
* @v data a pointer to the data recieved
* @v len length of data buffer
*/
-static void http_newdata ( struct tcp_connection *conn, void *data,
+static void http_newdata ( struct tcp_application *app, void *data,
size_t len ) {
- struct http_request *http = tcp_to_http ( conn );
+ struct http_request *http = tcp_to_http ( app );
char *content_length;
char *start = data;
char *rcp; int rc;
@@ -142,7 +142,7 @@ static void http_newdata ( struct tcp_connection *conn, void *data,
//printf("File recv is %d\n", http->file_recv);
if ( http->file_recv == http->file_size ){
http->state = HTTP_DONE;
- tcp_close(conn);
+ tcp_close(app);
}
break;
case HTTP_REQUEST_FILE:
@@ -155,10 +155,10 @@ static void http_newdata ( struct tcp_connection *conn, void *data,
/**
* Callback for sending TCP data
*
- * @v conn a TCP Connection
+ * @v app a TCP Application
*/
-static void http_senddata ( struct tcp_connection *conn, void *buf, size_t len ) {
- struct http_request *http = tcp_to_http ( conn );
+static void http_senddata ( struct tcp_application *app, void *buf, size_t len ) {
+ struct http_request *http = tcp_to_http ( app );
switch ( http->state ){
case HTTP_REQUEST_FILE:
@@ -166,13 +166,13 @@ static void http_senddata ( struct tcp_connection *conn, void *buf, size_t len )
printf("%s\n",(char *)buf);
// string is: GET <file> HTTP/1.0\r\n\r\n
- tcp_send ( conn, buf, len);
+ tcp_send ( app, buf, len);
break;
case HTTP_PARSE_HEADER:
case HTTP_RECV_FILE:
break;
case HTTP_DONE:
- //tcp_close(conn)
+ //tcp_close(app)
break;
default:
break;
@@ -193,8 +193,12 @@ static struct tcp_operations http_tcp_operations = {
* @v http a HTTP request
*/
struct async_operation * get_http ( struct http_request *http ) {
+ int rc;
+
http->tcp.tcp_op = &http_tcp_operations;
http->state = HTTP_REQUEST_FILE;
- tcp_connect ( &http->tcp );
+ if ( ( rc = tcp_connect ( &http->tcp, &http->server, 0 ) ) != 0 )
+ async_done ( &http->aop, rc );
+
return &http->aop;
}
diff --git a/src/net/tcp/iscsi.c b/src/net/tcp/iscsi.c
index a0137dc56..f3e5e3247 100644
--- a/src/net/tcp/iscsi.c
+++ b/src/net/tcp/iscsi.c
@@ -720,11 +720,14 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data,
/* Check for login redirection */
if ( response->status_class == ISCSI_STATUS_REDIRECT ) {
DBG ( "iSCSI %p redirecting to new server\n", iscsi );
- /* Close the TCP connection; our TCP closed() method
- * will take care of the reconnection once this
- * connection has been cleanly terminated.
- */
tcp_close ( &iscsi->tcp );
+ iscsi->status = 0;
+ if ( ( rc = tcp_connect ( &iscsi->tcp, &iscsi->target,
+ 0 ) ) != 0 ) {
+ DBG ( "iSCSI %p could not open TCP connection\n",
+ iscsi );
+ iscsi_done ( iscsi, rc );
+ }
return;
}
@@ -778,8 +781,8 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data,
*/
static inline struct iscsi_session *
-tcp_to_iscsi ( struct tcp_connection *conn ) {
- return container_of ( conn, struct iscsi_session, tcp );
+tcp_to_iscsi ( struct tcp_application *app ) {
+ return container_of ( app, struct iscsi_session, tcp );
}
/**
@@ -859,8 +862,8 @@ static void iscsi_tx_done ( struct iscsi_session *iscsi ) {
* Updates iscsi->tx_offset and, if applicable, transitions to the
* next TX state.
*/
-static void iscsi_acked ( struct tcp_connection *conn, size_t len ) {
- struct iscsi_session *iscsi = tcp_to_iscsi ( conn );
+static void iscsi_acked ( struct tcp_application *app, size_t len ) {
+ struct iscsi_session *iscsi = tcp_to_iscsi ( app );
struct iscsi_bhs_common *common = &iscsi->tx_bhs.common;
enum iscsi_tx_state next_state;
@@ -916,9 +919,9 @@ static void iscsi_acked ( struct tcp_connection *conn, size_t len ) {
*
* Constructs data to be sent for the current TX state
*/
-static void iscsi_senddata ( struct tcp_connection *conn,
+static void iscsi_senddata ( struct tcp_application *app,
void *buf, size_t len ) {
- struct iscsi_session *iscsi = tcp_to_iscsi ( conn );
+ struct iscsi_session *iscsi = tcp_to_iscsi ( app );
struct iscsi_bhs_common *common = &iscsi->tx_bhs.common;
static const char pad[] = { '\0', '\0', '\0' };
@@ -927,7 +930,7 @@ static void iscsi_senddata ( struct tcp_connection *conn,
/* Nothing to send */
break;
case ISCSI_TX_BHS:
- tcp_send ( conn, &iscsi->tx_bhs.bytes[iscsi->tx_offset],
+ tcp_send ( app, &iscsi->tx_bhs.bytes[iscsi->tx_offset],
( sizeof ( iscsi->tx_bhs ) - iscsi->tx_offset ) );
break;
case ISCSI_TX_AHS:
@@ -938,7 +941,7 @@ static void iscsi_senddata ( struct tcp_connection *conn,
iscsi_tx_data ( iscsi, buf, len );
break;
case ISCSI_TX_DATA_PADDING:
- tcp_send ( conn, pad, ( ISCSI_DATA_PAD_LEN ( common->lengths )
+ tcp_send ( app, pad, ( ISCSI_DATA_PAD_LEN ( common->lengths )
- iscsi->tx_offset ) );
break;
default:
@@ -1029,7 +1032,7 @@ static void iscsi_rx_bhs ( struct iscsi_session *iscsi, void *data,
/**
* Receive new data
*
- * @v tcp TCP connection
+ * @v tcp TCP application
* @v data Received data
* @v len Length of received data
*
@@ -1040,9 +1043,9 @@ static void iscsi_rx_bhs ( struct iscsi_session *iscsi, void *data,
* always has a full copy of the BHS available, even for portions of
* the data in different packets to the BHS.
*/
-static void iscsi_newdata ( struct tcp_connection *conn, void *data,
+static void iscsi_newdata ( struct tcp_application *app, void *data,
size_t len ) {
- struct iscsi_session *iscsi = tcp_to_iscsi ( conn );
+ struct iscsi_session *iscsi = tcp_to_iscsi ( app );
struct iscsi_bhs_common *common = &iscsi->rx_bhs.common;
void ( *process ) ( struct iscsi_session *iscsi, void *data,
size_t len, size_t remaining );
@@ -1098,38 +1101,28 @@ static void iscsi_newdata ( struct tcp_connection *conn, void *data,
}
}
-#warning "Remove me soon"
-static struct tcp_operations iscsi_tcp_operations;
-
/**
* Handle TCP connection closure
*
- * @v conn TCP connection
+ * @v app TCP application
* @v status Error code, if any
*
*/
-static void iscsi_closed ( struct tcp_connection *conn, int status ) {
- struct iscsi_session *iscsi = tcp_to_iscsi ( conn );
- int session_status = iscsi->status;
+static void iscsi_closed ( struct tcp_application *app, int status ) {
+ struct iscsi_session *iscsi = tcp_to_iscsi ( app );
+ int rc;
/* Clear session status */
iscsi->status = 0;
- /* If we are deliberately closing down, exit cleanly */
- if ( session_status & ISCSI_STATUS_CLOSING ) {
- iscsi_done ( iscsi, status );
- return;
- }
-
/* Retry connection if within the retry limit, otherwise fail */
if ( ++iscsi->retry_count <= ISCSI_MAX_RETRIES ) {
DBG ( "iSCSI %p retrying connection\n", iscsi );
- /* Re-copy address to handle redirection */
- memset ( &iscsi->tcp, 0, sizeof ( iscsi->tcp ) );
- iscsi->tcp.tcp_op = &iscsi_tcp_operations;
- memcpy ( &iscsi->tcp.peer, &iscsi->target,
- sizeof ( iscsi->tcp.peer ) );
- tcp_connect ( conn );
+ if ( ( rc = tcp_connect ( app, &iscsi->target, 0 ) ) != 0 ) {
+ DBG ( "iSCSI %p could not open TCP connection\n",
+ iscsi );
+ iscsi_done ( iscsi, rc );
+ }
} else {
printf ( "iSCSI %p retry count exceeded\n", iscsi );
iscsi_done ( iscsi, status );
@@ -1139,11 +1132,11 @@ static void iscsi_closed ( struct tcp_connection *conn, int status ) {
/**
* Handle TCP connection opening
*
- * @v conn TCP connection
+ * @v app TCP application
*
*/
-static void iscsi_connected ( struct tcp_connection *conn ) {
- struct iscsi_session *iscsi = tcp_to_iscsi ( conn );
+static void iscsi_connected ( struct tcp_application *app ) {
+ struct iscsi_session *iscsi = tcp_to_iscsi ( app );
/* Set connected flag and reset retry count */
iscsi->status = ( ISCSI_STATUS_SECURITY_NEGOTIATION_PHASE |
@@ -1179,6 +1172,8 @@ static struct tcp_operations iscsi_tcp_operations = {
*/
struct async_operation * iscsi_issue ( struct iscsi_session *iscsi,
struct scsi_command *command ) {
+ int rc;
+
assert ( iscsi->command == NULL );
iscsi->command = command;
@@ -1198,9 +1193,12 @@ struct async_operation * iscsi_issue ( struct iscsi_session *iscsi,
} else {
/* Session not open: initiate login */
iscsi->tcp.tcp_op = &iscsi_tcp_operations;
- memcpy ( &iscsi->tcp.peer, &iscsi->target,
- sizeof ( iscsi->tcp.peer ) );
- tcp_connect ( &iscsi->tcp );
+ if ( ( rc = tcp_connect ( &iscsi->tcp, &iscsi->target,
+ 0 ) ) != 0 ) {
+ DBG ( "iSCSI %p could not open TCP connection\n",
+ iscsi );
+ iscsi_done ( iscsi, rc );
+ }
}
return &iscsi->aop;
@@ -1212,10 +1210,7 @@ struct async_operation * iscsi_issue ( struct iscsi_session *iscsi,
* @v iscsi iSCSI session
* @ret aop Asynchronous operation
*/
-struct async_operation * iscsi_shutdown ( struct iscsi_session *iscsi ) {
- if ( iscsi->status ) {
- iscsi->status |= ISCSI_STATUS_CLOSING;
- tcp_close ( &iscsi->tcp );
- }
- return &iscsi->aop;
+void iscsi_shutdown ( struct iscsi_session *iscsi ) {
+ iscsi->status = 0;
+ tcp_close ( &iscsi->tcp );
}