Merge changes from mcb-tcp-fixes branch.
authorMichael Brown <mcb30@etherboot.org>
Wed, 27 Dec 2006 23:09:46 +0000 (23:09 +0000)
committerMichael Brown <mcb30@etherboot.org>
Wed, 27 Dec 2006 23:09:46 +0000 (23:09 +0000)
14 files changed:
src/drivers/scsi/iscsidev.c
src/include/gpxe/ftp.h
src/include/gpxe/hello.h
src/include/gpxe/http.h
src/include/gpxe/iscsi.h
src/include/gpxe/tcp.h
src/net/tcp.c
src/net/tcp/ftp.c
src/net/tcp/hello.c
src/net/tcp/http.c
src/net/tcp/iscsi.c
src/tests/ftptest.c
src/tests/hellotest.c
src/tests/httptest.c

index 9ee625c..b92d4bc 100644 (file)
@@ -63,5 +63,5 @@ int init_iscsidev ( struct iscsi_device *iscsidev ) {
  * @v iscsidev         iSCSI device
  */
 void fini_iscsidev ( struct iscsi_device *iscsidev ) {
-       async_wait ( iscsi_shutdown ( &iscsidev->iscsi ) );
+       iscsi_shutdown ( &iscsidev->iscsi );
 }
index 1a0861e..c3eafb6 100644 (file)
@@ -36,8 +36,8 @@ enum ftp_state {
  *
  */
 struct ftp_request {
-       /** TCP connection for this request */
-       struct tcp_connection tcp;
+       /** Server address */
+       struct sockaddr_tcpip server;
        /** File to download */
        const char *filename;
        /** Callback function
@@ -49,10 +49,6 @@ struct ftp_request {
         * remote server.
         */
        void ( *callback ) ( char *data, size_t len );
-       /** Eventual return status */
-       int rc;
-       /** Asynchronous operation for this FTP operation */
-       struct async_operation aop;
 
        /** Current state */
        enum ftp_state state;
@@ -67,8 +63,13 @@ struct ftp_request {
        /** Passive-mode parameters, as text */
        char passive_text[24]; /* "aaa,bbb,ccc,ddd,eee,fff" */
 
-       /** TCP connection for the data channel */
-       struct tcp_connection tcp_data;
+       /** TCP application for the control channel */
+       struct tcp_application tcp;
+       /** TCP application for the data channel */
+       struct tcp_application tcp_data;
+
+       /** Asynchronous operation for this FTP operation */
+       struct async_operation aop;
 };
 
 struct async_operation * ftp_get ( struct ftp_request *ftp );
index cdd26ad..18d0b37 100644 (file)
@@ -21,14 +21,10 @@ enum hello_state {
  *
  */
 struct hello_request {
-       /** TCP connection for this request */
-       struct tcp_connection tcp;
-       /** Current state */
-       enum hello_state state;
+       /** Server to connect to */
+       struct sockaddr_tcpip server;
        /** Message to be transmitted */
        const char *message;
-       /** Amount of message remaining to be transmitted */
-       size_t remaining;
        /** Callback function
         *
         * @v data      Received data
@@ -38,6 +34,15 @@ struct hello_request {
         * remote server.
         */
        void ( *callback ) ( char *data, size_t len );
+
+       /** Current state */
+       enum hello_state state;
+       /** Amount of message remaining to be transmitted */
+       size_t remaining;
+
+       /** TCP application for this request */
+       struct tcp_application tcp;
+
        /** Asynchronous operation */
        struct async_operation aop;
 };
index 02c9be4..f858c5f 100644 (file)
@@ -29,8 +29,10 @@ enum http_state {
 struct http_request;
 
 struct http_request {
-       /** TCP connection for this request */
-       struct tcp_connection tcp;
+       /** Server address */
+       struct sockaddr_tcpip server;
+       /** TCP application for this request */
+       struct tcp_application tcp;
        /** Current state */
        enum http_state state;
         /** File to download */
index 1b9ef92..526ef43 100644 (file)
@@ -486,40 +486,33 @@ enum iscsi_rx_state {
 
 /** An iSCSI session */
 struct iscsi_session {
-       /** TCP connection for this session */
-       struct tcp_connection tcp;
+       /** Initiator IQN */
+       const char *initiator_iqn;
+       /** Target address */
+       struct sockaddr_tcpip target;
+       /** Target IQN */
+       const char *target_iqn;
+       /** Logical Unit Number (LUN) */
+       uint64_t lun;
+       /** Username */
+       const char *username;
+       /** Password */
+       const char *password;
+
+       /** TCP application for this session */
+       struct tcp_application tcp;
        /** Session status
         *
         * This is the bitwise-OR of zero or more ISCSI_STATUS_XXX
         * constants.
         */
        int status;
-       /** Asynchronous operation for the current iSCSI operation */
-       struct async_operation aop;
        /** Retry count
         *
         * Number of times that the connection has been retried.
         * Reset upon a successful connection.
         */
        int retry_count;
-
-       /** Initiator IQN */
-       const char *initiator_iqn;
-       /** Target address
-        *
-        * Kept separate from the TCP connection structure because we
-        * may need to handle login redirection.
-        */
-       struct sockaddr_tcpip target;
-       /** Target IQN */
-       const char *target_iqn;
-       /** Logical Unit Number (LUN) */
-       uint64_t lun;
-
-       /** Username */
-       const char *username;
-       /** Password */
-       const char *password;
        /** CHAP challenge/response */
        struct chap_challenge chap;
 
@@ -597,6 +590,8 @@ struct iscsi_session {
         * Set to NULL when command is complete.
         */
        struct scsi_command *command;
+       /** Asynchronous operation for the current iSCSI operation */
+       struct async_operation aop;
 };
 
 /** iSCSI session is currently in the security negotiation phase */
@@ -632,15 +627,12 @@ struct iscsi_session {
 /** Mask for all iSCSI "needs to send" flags */
 #define ISCSI_STATUS_STRINGS_MASK 0xff00
 
-/** iSCSI session is closing down */
-#define ISCSI_STATUS_CLOSING 0x00010000
-
 /** Maximum number of retries at connecting */
 #define ISCSI_MAX_RETRIES 2
 
 extern struct async_operation * iscsi_issue ( struct iscsi_session *iscsi,
                                              struct scsi_command *command );
-extern struct async_operation * iscsi_shutdown ( struct iscsi_session *iscsi );
+extern void iscsi_shutdown ( struct iscsi_session *iscsi );
 
 /** An iSCSI device */
 struct iscsi_device {
index 9afb061..f5df8cb 100644 (file)
  *
  */
 
-#include <stddef.h>
-#include <gpxe/list.h>
+#include "latch.h"
 #include <gpxe/tcpip.h>
-#include <gpxe/pkbuff.h>
-#include <gpxe/retry.h>
 
-struct tcp_connection;
+/**
+ * A TCP header
+ */
+struct tcp_header {
+       uint16_t src;           /* Source port */
+       uint16_t dest;          /* Destination port */
+       uint32_t seq;           /* Sequence number */
+       uint32_t ack;           /* Acknowledgement number */
+       uint8_t hlen;           /* Header length (4), Reserved (4) */
+       uint8_t flags;          /* Reserved (2), Flags (6) */
+       uint16_t win;           /* Advertised window */
+       uint16_t csum;          /* Checksum */
+       uint16_t urg;           /* Urgent pointer */
+};
+
+/*
+ * TCP flags
+ */
+#define TCP_CWR                0x80
+#define TCP_ECE                0x40
+#define TCP_URG                0x20
+#define TCP_ACK                0x10
+#define TCP_PSH                0x08
+#define TCP_RST                0x04
+#define TCP_SYN                0x02
+#define TCP_FIN                0x01
+
+/**
+* @defgroup tcpstates TCP states
+*
+* The TCP state is defined by a combination of the flags that are
+* currently being sent in outgoing packets, the flags that have been
+* sent and acknowledged by the peer, and the flags that have been
+* received from the peer.
+*
+* @{
+*/
+
+/** TCP flags that are currently being sent in outgoing packets */
+#define TCP_STATE_SENDING(flags) ( (flags) << 0 )
+#define TCP_FLAGS_SENDING(state) ( ( (state) >> 0 ) & 0xff )
+
+/** TCP flags that have been acknowledged by the peer
+ *
+ * Note that this applies only to SYN and FIN.
+ */
+#define TCP_STATE_ACKED(flags) ( (flags) << 8 )
+#define TCP_FLAGS_ACKED(state) ( ( (state) >> 8 ) & 0x03 )
+
+/** TCP flags that have been received from the peer
+ *
+ * Note that this applies only to SYN and FIN, and that once SYN has
+ * been received, we should always be sending ACK.
+ */
+#define TCP_STATE_RCVD(flags) ( (flags) << 12 )
+#define TCP_FLAGS_RCVD(state) ( ( (state) >> 12 ) & 0x03 )
+
+/** CLOSED
+ *
+ * The connection has not yet been used for anything.
+ */
+#define TCP_CLOSED TCP_RST
+
+/** LISTEN
+ *
+ * Not currently used as a state; we have no support for listening
+ * connections.  Given a unique value to avoid compiler warnings.
+ */
+#define TCP_LISTEN 0
+
+/** SYN_SENT
+ *
+ * SYN has been sent, nothing has yet been received or acknowledged.
+ */
+#define TCP_SYN_SENT   ( TCP_STATE_SENDING ( TCP_SYN ) )
+
+/** SYN_RCVD
+ *
+ * SYN has been sent but not acknowledged, SYN has been received.
+ */
+#define TCP_SYN_RCVD   ( TCP_STATE_SENDING ( TCP_SYN | TCP_ACK ) |     \
+                         TCP_STATE_RCVD ( TCP_SYN ) )
+
+/** ESTABLISHED
+ *
+ * SYN has been sent and acknowledged, SYN has been received.
+ */
+#define TCP_ESTABLISHED        ( TCP_STATE_SENDING ( TCP_ACK ) |               \
+                         TCP_STATE_ACKED ( TCP_SYN ) |                 \
+                         TCP_STATE_RCVD ( TCP_SYN ) )
+
+/** FIN_WAIT_1
+ *
+ * SYN has been sent and acknowledged, SYN has been received, FIN has
+ * been sent but not acknowledged, FIN has not been received.
+ *
+ * RFC 793 shows that we can enter FIN_WAIT_1 without have had SYN
+ * acknowledged, i.e. if the application closes the connection after
+ * sending and receiving SYN, but before having had SYN acknowledged.
+ * However, we have to *pretend* that SYN has been acknowledged
+ * anyway, otherwise we end up sending SYN and FIN in the same
+ * sequence number slot.  Therefore, when we transition from SYN_RCVD
+ * to FIN_WAIT_1, we have to remember to set TCP_STATE_ACKED(TCP_SYN)
+ * and increment our sequence number.
+ */
+#define TCP_FIN_WAIT_1 ( TCP_STATE_SENDING ( TCP_ACK | TCP_FIN ) |     \
+                         TCP_STATE_ACKED ( TCP_SYN ) |                 \
+                         TCP_STATE_RCVD ( TCP_SYN ) )
+
+/** FIN_WAIT_2
+ *
+ * SYN has been sent and acknowledged, SYN has been received, FIN has
+ * been sent and acknowledged, FIN ha not been received.
+ */
+#define TCP_FIN_WAIT_2 ( TCP_STATE_SENDING ( TCP_ACK ) |               \
+                         TCP_STATE_ACKED ( TCP_SYN | TCP_FIN ) |       \
+                         TCP_STATE_RCVD ( TCP_SYN ) )
+
+/** CLOSING / LAST_ACK
+ *
+ * SYN has been sent and acknowledged, SYN has been received, FIN has
+ * been sent but not acknowledged, FIN has been received.
+ *
+ * This state actually encompasses both CLOSING and LAST_ACK; they are
+ * identical with the definition of state that we use.  I don't
+ * *believe* that they need to be distinguished.
+ */
+#define TCP_CLOSING_OR_LAST_ACK                                        \
+                       ( TCP_STATE_SENDING ( TCP_ACK | TCP_FIN ) |     \
+                         TCP_STATE_ACKED ( TCP_SYN ) |                 \
+                         TCP_STATE_RCVD ( TCP_SYN | TCP_FIN ) )
+
+/** TIME_WAIT
+ *
+ * SYN has been sent and acknowledged, SYN has been received, FIN has
+ * been sent and acknowledged, FIN has been received.
+ */
+#define TCP_TIME_WAIT  ( TCP_STATE_SENDING ( TCP_ACK ) |               \
+                         TCP_STATE_ACKED ( TCP_SYN | TCP_FIN ) |       \
+                         TCP_STATE_RCVD ( TCP_SYN | TCP_FIN ) )
+
+/** CLOSE_WAIT
+ *
+ * SYN has been sent and acknowledged, SYN has been received, FIN has
+ * been received.
+ */
+#define TCP_CLOSE_WAIT ( TCP_STATE_SENDING ( TCP_ACK ) |               \
+                         TCP_STATE_ACKED ( TCP_SYN ) |                 \
+                         TCP_STATE_RCVD ( TCP_SYN | TCP_FIN ) )
+
+/** Can send data in current state
+ *
+ * We can send data if and only if we have had our SYN acked and we
+ * have not yet sent our FIN.
+ */
+#define TCP_CAN_SEND_DATA(state)                                       \
+       ( ( (state) & ( TCP_STATE_ACKED ( TCP_SYN | TCP_FIN ) |         \
+                     TCP_STATE_SENDING ( TCP_FIN ) ) )                 \
+         == TCP_STATE_ACKED ( TCP_SYN ) )
+
+/** Have closed gracefully
+ *
+ * We have closed gracefully if we have both received a FIN and had
+ * our own FIN acked.
+ */
+#define TCP_CLOSED_GRACEFULLY(state)                                   \
+       ( ( (state) & ( TCP_STATE_ACKED ( TCP_FIN ) |                   \
+                       TCP_STATE_RCVD ( TCP_FIN ) ) )                  \
+         == ( TCP_STATE_ACKED ( TCP_FIN ) | TCP_STATE_RCVD ( TCP_FIN ) ) )
+
+/** @} */
+
+/** Mask for TCP header length field */
+#define TCP_MASK_HLEN  0xf0
+
+/** Smallest port number on which a TCP connection can listen */
+#define TCP_MIN_PORT 1
+
+/* Some PKB constants */
+#define MAX_HDR_LEN    100
+#define MAX_PKB_LEN    1500
+#define MIN_PKB_LEN    MAX_HDR_LEN + 100 /* To account for padding by LL */
+
+/**
+ * Advertised TCP window size
+ *
+ * Our TCP window is actually limited by the amount of space available
+ * for RX packets in the NIC's RX ring; we tend to populate the rings
+ * with far fewer descriptors than a typical driver.  Since we have no
+ * way of knowing how much of this RX ring space will be available for
+ * received TCP packets (consider, for example, that they may all be
+ * consumed by a series of unrelated ARP requests between other
+ * machines on the network), it is actually not even theoretically
+ * possible for us to specify an accurate window size.  We therefore
+ * guess an arbitrary number that is empirically as large as possible
+ * while avoiding retransmissions due to dropped packets.
+ */
+#define TCP_WINDOW_SIZE        2048
+
+/** TCP maximum segment lifetime
+ *
+ * Currently set to 2 minutes, as per RFC 793.
+ */
+#define TCP_MSL ( 2 * 60 * TICKS_PER_SEC )
+
+struct tcp_application;
 
 /**
  * TCP operations
@@ -25,7 +227,7 @@ struct tcp_operations {
        /*
         * Connection closed
         *
-        * @v conn      TCP connection
+        * @v app       TCP application
         * @v status    Error code, if any
         *
         * This is called when the connection is closed for any
@@ -33,42 +235,41 @@ struct tcp_operations {
         * contains the negative error number, if the closure is due
         * to an error.
         *
-        * Note that acked() and newdata() may be called after
-        * closed(), if the packet containing the FIN also
-        * acknowledged data or contained new data.  Note also that
-        * connected() may not have been called before closed(), if
-        * the close is due to an error.
+        * When closed() is called, the application no longer has a
+        * valid TCP connection.  Note that connected() may not have
+        * been called before closed(), if the close is due to an
+        * error during connection setup.
         */
-       void ( * closed ) ( struct tcp_connection *conn, int status );
+       void ( * closed ) ( struct tcp_application *app, int status );
        /**
-        * Connection established (SYNACK received)
+        * Connection established
         *
-        * @v conn      TCP connection
+        * @v app       TCP application
         */
-       void ( * connected ) ( struct tcp_connection *conn );
+       void ( * connected ) ( struct tcp_application *app );
        /**
         * Data acknowledged
         *
-        * @v conn      TCP connection
+        * @v app       TCP application
         * @v len       Length of acknowledged data
         *
         * @c len is guaranteed to not exceed the outstanding amount
         * of unacknowledged data.
         */
-       void ( * acked ) ( struct tcp_connection *conn, size_t len );
+       void ( * acked ) ( struct tcp_application *app, size_t len );
        /**
         * New data received
         *
-        * @v conn      TCP connection
+        * @v app       TCP application
         * @v data      Data
         * @v len       Length of data
         */
-       void ( * newdata ) ( struct tcp_connection *conn,
+       void ( * newdata ) ( struct tcp_application *app,
                             void *data, size_t len );
        /**
         * Transmit data
         *
-        * @v conn      TCP connection
+        * @v app       TCP application
         * @v buf       Temporary data buffer
         * @v len       Length of temporary data buffer
         *
@@ -86,137 +287,36 @@ struct tcp_operations {
         * the buffer is not compulsory; the application may call
         * tcp_send() on any block of data.
         */
-       void ( * senddata ) ( struct tcp_connection *conn, void *buf,
+       void ( * senddata ) ( struct tcp_application *app, void *buf,
                              size_t len );
 };
 
-#if USE_UIP
+struct tcp_connection;
 
 /**
- * A TCP connection
+ * A TCP application
  *
+ * This data structure represents an application with a TCP connection.
  */
-struct tcp_connection {
-       /** Address of the remote end of the connection */
-       struct sockaddr_in sin;
-       /** Operations table for this connection */
+struct tcp_application {
+       /** TCP connection data
+        *
+        * This is filled in by TCP calls that initiate a connection,
+        * and reset to NULL when the connection is closed.
+        */
+       struct tcp_connection *conn;
+       /** TCP connection operations table */
        struct tcp_operations *tcp_op;
 };
 
-extern void tcp_connect ( struct tcp_connection *conn );
-extern void tcp_send ( struct tcp_connection *conn, const void *data,
-                      size_t len );
-extern void tcp_kick ( struct tcp_connection *conn );
-extern void tcp_close ( struct tcp_connection *conn );
-
-#else
-
-#define TCP_NOMSG ""
-#define TCP_NOMSG_LEN 0
-
-/* Smallest port number on which a TCP connection can listen */
-#define TCP_MIN_PORT 1
-
-/* Some PKB constants */
-#define MAX_HDR_LEN    100
-#define MAX_PKB_LEN    1500
-#define MIN_PKB_LEN    MAX_HDR_LEN + 100 /* To account for padding by LL */
-
-/**
- * TCP states
- */
-#define TCP_CLOSED     0
-#define TCP_LISTEN     1
-#define TCP_SYN_SENT   2
-#define TCP_SYN_RCVD   3
-#define TCP_ESTABLISHED        4
-#define TCP_FIN_WAIT_1 5
-#define TCP_FIN_WAIT_2 6
-#define TCP_CLOSING    7
-#define TCP_TIME_WAIT  8
-#define TCP_CLOSE_WAIT 9
-#define TCP_LAST_ACK   10
-
-#define TCP_INVALID    11
-
-/**
- * A TCP connection
- */
-struct tcp_connection {
-       struct sockaddr_tcpip peer;     /* Remote socket address */
-       uint16_t local_port;            /* Local port, in network byte order */
-       int tcp_state;                  /* TCP state */
-       int tcp_lstate;                 /* Last TCP state */
-       uint32_t snd_una;               /* Lowest unacked byte on snd stream */
-       uint32_t snd_win;               /* Offered by remote end */
-       uint32_t rcv_nxt;               /* Next expected byte on rcv stream */
-       uint32_t rcv_win;               /* Advertised to receiver */
-       uint8_t tcp_flags;              /* TCP header flags */
-       struct list_head list;          /* List of TCP connections */
-       struct pk_buff *tx_pkb;         /* Transmit packet buffer */
-       struct retry_timer timer;       /* Retransmission timer */
-       struct tcp_operations *tcp_op;  /* Operations table for connection */
-};
-
-/** Retry timer values */
-#define MAX_RETRANSMITS        3
-
-/**
- * Connection closed status codes
- */
-#define CONN_SNDCLOSE  0
-#define CONN_RESTART   1
-#define CONN_TIMEOUT   2
-#define CONN_RCVCLOSE  3
-
-/**
- * A TCP header
- */
-struct tcp_header {
-       uint16_t src;           /* Source port */
-       uint16_t dest;          /* Destination port */
-       uint32_t seq;           /* Sequence number */
-       uint32_t ack;           /* Acknowledgement number */
-       uint8_t hlen;           /* Header length (4), Reserved (4) */
-       uint8_t flags;          /* Reserved (2), Flags (6) */
-       uint16_t win;           /* Advertised window */
-       uint16_t csum;          /* Checksum */
-       uint16_t urg;           /* Urgent pointer */
-};
-
-/**
- * TCP masks
- */
-#define TCP_MASK_HLEN  0xf0
-#define TCP_MASK_FLAGS 0x3f
-
-/**
- * TCP flags
- */
-#define TCP_URG                0x20
-#define TCP_ACK                0x10
-#define TCP_PSH                0x08
-#define TCP_RST                0x04
-#define TCP_SYN                0x02
-#define TCP_FIN                0x01
-
-extern struct tcpip_protocol tcp_protocol;
-
-static inline int tcp_closed ( struct tcp_connection *conn ) {
-       return ( conn->tcp_state == TCP_CLOSED );
-}
-
-extern void tcp_init_conn ( struct tcp_connection *conn );
-extern int tcp_connect ( struct tcp_connection *conn );
-extern int tcp_connectto ( struct tcp_connection *conn,
-                          struct sockaddr_tcpip *peer );
-extern int tcp_listen ( struct tcp_connection *conn, uint16_t port );
-extern int tcp_senddata ( struct tcp_connection *conn );
-extern int tcp_close ( struct tcp_connection *conn );
-
-extern int tcp_send ( struct tcp_connection *conn, const void *data, 
+extern int tcp_connect ( struct tcp_application *app,
+                        struct sockaddr_tcpip *peer,
+                        uint16_t local_port );
+extern void tcp_close ( struct tcp_application *app );
+extern int tcp_senddata ( struct tcp_application *app );
+extern int tcp_send ( struct tcp_application *app, const void *data, 
                      size_t len );
 
-#endif /* USE_UIP */
+extern struct tcpip_protocol tcp_protocol;
 
 #endif /* _GPXE_TCP_H */
index 2a18828..9cb2cf7 100644 (file)
 #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 */
index 6082d97..1bdf813 100644 (file)
@@ -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;
 }
index d74e52f..4de7e87 100644 (file)
@@ -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.
  *   }
  *
  *   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
  */
 
 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;
 }
index 01f0aea..3bba8b3 100644 (file)
  */
 
 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;
 }
index a0137dc..f3e5e32 100644 (file)
@@ -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 );
 }
index 6b0002f..c4795c0 100644 (file)
@@ -29,7 +29,7 @@ void test_ftp ( struct sockaddr_tcpip *server, const char *filename ) {
        printf ( "FTP fetching %s\n", filename );
        
        memset ( &ftp, 0, sizeof ( ftp ) );
-       memcpy ( &ftp.tcp.peer, server, sizeof ( ftp.tcp.peer ) );
+       memcpy ( &ftp.server, server, sizeof ( ftp.server ) );
        ftp.filename = filename;
        ftp.callback = test_ftp_callback;
 
index 4565f13..a873c03 100644 (file)
@@ -32,7 +32,7 @@ void test_hello ( struct sockaddr_tcpip *server, const char *message ) {
                 inet_ntoa ( sin->sin_addr ), ntohs ( sin->sin_port ) );
        
        memset ( &hello, 0, sizeof ( hello ) );
-       memcpy ( &hello.tcp.peer, server, sizeof ( hello.tcp.peer ) );
+       memcpy ( &hello.server, server, sizeof ( hello.server ) );
        hello.message = message;
        hello.callback = test_hello_callback;
 
index 4b569e8..7e08f07 100644 (file)
@@ -21,7 +21,7 @@ void test_http ( struct net_device *netdev, struct sockaddr_tcpip *server, const
        int rc;
 
        memset ( &http, 0, sizeof ( http ) );
-       memcpy ( &http.tcp.peer, server, sizeof ( http.tcp.peer ) );
+       memcpy ( &http.server, server, sizeof ( http.server ) );
        http.filename = filename;
        http.callback = test_http_callback;