From 61ed298bc7dc60c84fea456444e853a73de0c901 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 27 Dec 2006 23:09:46 +0000 Subject: [PATCH] Merge changes from mcb-tcp-fixes branch. --- src/drivers/scsi/iscsidev.c | 2 +- src/include/gpxe/ftp.h | 17 +- src/include/gpxe/hello.h | 17 +- src/include/gpxe/http.h | 6 +- src/include/gpxe/iscsi.h | 44 +- src/include/gpxe/tcp.h | 380 +++++--- src/net/tcp.c | 1628 ++++++++++++++++------------------- src/net/tcp/ftp.c | 158 ++-- src/net/tcp/hello.c | 44 +- src/net/tcp/http.c | 48 +- src/net/tcp/iscsi.c | 87 +- src/tests/ftptest.c | 2 +- src/tests/hellotest.c | 2 +- src/tests/httptest.c | 2 +- 14 files changed, 1178 insertions(+), 1259 deletions(-) diff --git a/src/drivers/scsi/iscsidev.c b/src/drivers/scsi/iscsidev.c index 9ee625c4..b92d4bc9 100644 --- a/src/drivers/scsi/iscsidev.c +++ b/src/drivers/scsi/iscsidev.c @@ -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 ); } diff --git a/src/include/gpxe/ftp.h b/src/include/gpxe/ftp.h index 1a0861e9..c3eafb67 100644 --- a/src/include/gpxe/ftp.h +++ b/src/include/gpxe/ftp.h @@ -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 ); diff --git a/src/include/gpxe/hello.h b/src/include/gpxe/hello.h index cdd26adf..18d0b378 100644 --- a/src/include/gpxe/hello.h +++ b/src/include/gpxe/hello.h @@ -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; }; diff --git a/src/include/gpxe/http.h b/src/include/gpxe/http.h index 02c9be41..f858c5ff 100644 --- a/src/include/gpxe/http.h +++ b/src/include/gpxe/http.h @@ -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 */ diff --git a/src/include/gpxe/iscsi.h b/src/include/gpxe/iscsi.h index 1b9ef926..526ef43b 100644 --- a/src/include/gpxe/iscsi.h +++ b/src/include/gpxe/iscsi.h @@ -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 { diff --git a/src/include/gpxe/tcp.h b/src/include/gpxe/tcp.h index 9afb061b..f5df8cba 100644 --- a/src/include/gpxe/tcp.h +++ b/src/include/gpxe/tcp.h @@ -9,13 +9,215 @@ * */ -#include -#include +#include "latch.h" #include -#include -#include -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 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 ); 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, - size_t len ); - -#endif /* USE_UIP */ - #endif /* _GPXE_TCP_H */ diff --git a/src/net/tcp.c b/src/net/tcp.c index 2a188287..9cb2cf7e 100644 --- a/src/net/tcp.c +++ b/src/net/tcp.c @@ -1,598 +1,729 @@ #include #include #include -#include -#include #include -#include -#include -#include +#include +#include +#include #include -#include -#include -#include #include -#include "uip/uip.h" +#include +#include /** @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 + * A TCP connection * - * 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 - * - * 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; -/** Size of #tcp_buffer */ -static size_t tcp_buflen = UIP_BUFSIZE - ( 40 + UIP_LLH_LEN ); + /** Remote socket address */ + struct sockaddr_tcpip peer; + /** Local port, in network byte order */ + uint16_t local_port; -/** - * Open a TCP connection - * - * @v conn TCP connection - * - * This sets up a new TCP connection to the remote host specified in - * tcp_connection::sin. - */ -void tcp_connect ( struct tcp_connection *conn ) { - struct uip_conn *uip_conn; - u16_t ipaddr[2]; + /** 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; - 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; -} - -/** - * Send data via a TCP connection - * - * @v conn TCP connection - * @v data Data to send - * @v len Length of data - * - * 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. - */ -void tcp_send ( struct tcp_connection *conn __unused, - const void *data, size_t len ) { - - assert ( conn = *( ( void ** ) uip_conn->appstate ) ); - - if ( len > tcp_buflen ) - len = tcp_buflen; - memmove ( tcp_buffer, data, len ); - - uip_send ( tcp_buffer, len ); -} - -/** - * Close a TCP connection - * - * @v conn TCP connection - */ -void tcp_close ( struct tcp_connection *conn __unused ) { - assert ( conn = *( ( void ** ) uip_conn->appstate ) ); - uip_close(); -} - -/** - * uIP TCP application call interface - * - * 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. - */ -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 ); -} - -/* Present here to allow everything to link. Will go into separate - * udp.c file - */ -void uip_udp_appcall ( void ) { -} - -/** - * Perform periodic processing of all TCP connections - * - * This allows TCP connections to retransmit data if necessary. - */ -static void tcp_periodic ( void ) { - struct pk_buff *pkb; - int i; - - 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 ); - - ipv4_uip_tx ( pkb ); - } - } -} - -/** - * Kick a connection into life - * - * @v conn TCP connection - * - * Call this function when you have new data to send and are not - * already being called as part of TCP processing. - */ -void tcp_kick ( struct tcp_connection *conn __unused ) { - /* Just kick all the connections; this will work for now */ - tcp_periodic(); -} - -/** - * Single-step the TCP stack - * - * @v process TCP process - * - * This calls tcp_periodic() at regular intervals. - */ -static void tcp_step ( struct process *process ) { - static unsigned long timeout = 0; - - if ( currticks() > timeout ) { - timeout = currticks() + ( TICKS_PER_SEC / 10 ); - tcp_periodic (); - } - - schedule ( process ); -} - -/** TCP stack process */ -static struct process tcp_process = { - .step = tcp_step, + /** 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; }; -/** Initialise the TCP stack */ -static void init_tcp ( void ) { - schedule ( &tcp_process ); -} - -INIT_FN ( INIT_PROCESS, init_tcp, NULL, NULL ); - -#else - /** * List of registered TCP connections */ static LIST_HEAD ( tcp_conns ); /** - * List of TCP states - */ -static const char *tcp_states[] = { - "CLOSED", - "LISTEN", - "SYN_SENT", - "SYN_RCVD", - "ESTABLISHED", - "FIN_WAIT_1", - "FIN_WAIT_2", - "CLOSING", - "TIME_WAIT", - "CLOSE_WAIT", - "LAST_ACK", - "INVALID" }; - -/** - * TCP state transition function + * Name TCP state * - * @v conn TCP connection - * @v nxt_state Next TCP state + * @v state TCP state + * @ret name Name of TCP state */ -void tcp_set_flags ( struct tcp_connection *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; +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"; } } -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 ); -} - /** - * Dump TCP header + * Dump TCP state transition * - * @v tcphdr TCP header + * @v conn TCP connection */ -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 ) ); +static inline __attribute__ (( always_inline )) void +tcp_dump_state ( struct tcp_connection *conn ) { + + 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; } /** - * Initialize a TCP connection + * Dump TCP flags + * + * @v flags TCP flags + */ +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" ); +} + +/** + * Allocate TCP connection + * + * @ret conn TCP connection, or NULL + * + * Allocates TCP connection and adds it to the TCP connection list. + */ +static struct tcp_connection * alloc_tcp ( void ) { + struct tcp_connection *conn; + + 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; +} + +/** + * Free TCP connection + * + * @v conn TCP connection + * + * Removes connection from TCP connection list and frees the data + * structure. + */ +static void free_tcp ( struct tcp_connection *conn ) { + + assert ( conn ); + assert ( conn->tcp_state == TCP_CLOSED ); + assert ( conn->app == NULL ); + + stop_timer ( &conn->timer ); + list_del ( &conn->list ); + free ( conn ); + DBG ( "TCP %p freed\n", conn ); +} + +/** + * Associate TCP connection with application + * + * @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 + * + * @v conn TCP connection + */ +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 ); + } +} + +/** + * Transmit any outstanding data + * + * @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. + * + * 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 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; + + /* 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 ); + + /* 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; + } + + /* 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; + } + + /* If we are transmitting anything that requires + * acknowledgement (i.e. consumes sequence space), start the + * retransmission timer. + */ + if ( seq_len ) + start_timer ( &conn->timer ); + + /* 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 ) ); + + /* 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" ); + + /* Transmit packet */ + return tcpip_tx ( pkb, &tcp_protocol, &conn->peer ); +} + +/** + * Transmit any outstanding data * * @v conn TCP connection - * - * This function assigns initial values to some fields in the connection - * structure. The application should call tcp_init_conn after creating a new - * connection before calling any other "tcp_*" function. - * - * struct tcp_connection my_conn; - * tcp_init_conn ( &my_conn ); - * ... + * + * This function allocates space to the transmit buffer and invokes + * the senddata() callback function, to allow the application to + * transmit new data. */ -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_senddata ( struct tcp_application *app ) { + struct tcp_connection *conn = app->conn; + + /* Check connection actually exists */ + if ( ! conn ) { + DBG ( "TCP app %p has no connection\n", app ); + return -ENOTCONN; + } + + return tcp_senddata_conn ( conn, 0 ); } -/** Retry timer +/** + * Transmit data + * + * @v app TCP application + * @v data Data to be sent + * @v len Length of the data + * @ret rc Return status code + * + * 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. + */ +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; +} + +/** + * 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 ( "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; + DBG ( "TCP %p timer %s in %s\n", conn, + ( over ? "expired" : "fired" ), tcp_state ( conn->tcp_state ) ); - 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; -} + 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 ) ); -/** - * 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; - - /* 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; - } - -#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. + /* If we have finally timed out and given up, or if this is + * the result of a graceful close, terminate the connection */ - conn->local_port = 0; + if ( over || graceful_close ) { - /* Add the connection to the set of listening connections */ - if ( ( rc = tcp_listen ( conn, conn->local_port ) ) != 0 ) { - return rc; + /* Transition to CLOSED */ + conn->tcp_state = TCP_CLOSED; + tcp_dump_state ( conn ); + + /* If we haven't closed gracefully, send a RST */ + if ( ! graceful_close ) + tcp_senddata_conn ( conn, 1 ); + + /* Break association between application and connection */ + tcp_disassociate ( conn ); + + /* Free the connection */ + free_tcp ( conn ); + + /* Notify application */ + if ( app && app->tcp_op->closed ) + app->tcp_op->closed ( app, -ETIMEDOUT ); + + } else { + /* Otherwise, retransmit the packet */ + tcp_senddata_conn ( conn, 0 ); } - memcpy ( &conn->peer, peer, sizeof ( conn->peer ) ); - - /* 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 ); -} - -int tcp_connect ( struct tcp_connection *conn ) { - return tcp_connectto ( conn, &conn->peer ); } /** - * Close the connection + * Identify TCP connection by local port number * - * @v conn - * - * This function sends a FIN packet to the remote end of the connection. When - * the remote end of the connection ACKs the FIN (FIN consumes one byte on the - * snd stream), the stack invokes the closed() callback function. + * @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 ); +static struct tcp_connection * tcp_demux ( uint16_t local_port ) { + struct tcp_connection *conn; + + list_for_each_entry ( conn, &tcp_conns, list ) { + if ( conn->local_port == local_port ) + return conn; + } + return NULL; +} + +/** + * Handle TCP received SYN + * + * @v conn TCP connection + * @v seq SEQ value (in host-endian order) + * @ret rc Return status code + */ +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; - 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; + + /* 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++; + + return 0; +} + +/** + * Handle TCP received ACK + * + * @v conn TCP connection + * @v ack ACK value (in host-endian order) + * @v win WIN value (in host-endian order) + * @ret rc Return status code + */ +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; } - 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 ); + /* 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.) + */ + 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--; + } + + /* Update SEQ and sent counters, and window size */ + conn->snd_seq = ack; + conn->snd_sent = 0; + conn->snd_win = win; + + /* 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; +} + +/** + * Handle TCP received 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 + */ +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 ); + + return 0; +} + +/** Handle TCP received FIN + * + * @v conn TCP connection + * @v seq SEQ value (in host-endian order) + * @ret rc Return status code + */ +static int tcp_rx_fin ( struct tcp_connection *conn, uint32_t seq ) { + struct tcp_application *app = conn->app; + + /* Ignore duplicate FIN */ + if ( ( conn->rcv_ack - seq ) > 0 ) + return 0; + + /* Mark FIN as received and send our own FIN */ + conn->tcp_state |= ( TCP_STATE_RCVD ( TCP_FIN ) | + TCP_STATE_SENDING ( TCP_FIN ) ); + + /* Acknowledge FIN */ + conn->rcv_ack++; + + /* Break association with application */ + tcp_disassociate ( conn ); + + /* 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 + */ +static int tcp_rx ( struct pk_buff *pkb, + struct sockaddr_tcpip *st_src __unused, + struct sockaddr_tcpip *st_dest __unused ) { + struct tcp_header *tcphdr; + struct tcp_connection *conn; + unsigned int hlen; + 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 ( "TCP packet too short at %d bytes (min %d bytes)\n", + pkb_len ( pkb ), sizeof ( *tcphdr ) ); + rc = -EINVAL; + goto err; + } + tcphdr = pkb->data; + hlen = ( ( tcphdr->hlen & TCP_MASK_HLEN ) / 16 ) * 4; + if ( hlen < sizeof ( *tcphdr ) ) { + DBG ( "TCP header too short at %d bytes (min %d bytes)\n", + hlen, sizeof ( *tcphdr ) ); + rc = -EINVAL; + goto err; + } + 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" + + /* 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++; + } + + /* Handle new data, if any */ + tcp_rx_data ( conn, seq, data, len ); + seq += len; + + /* Handle FIN, if present */ + if ( flags & TCP_FIN ) { + tcp_rx_fin ( conn, seq ); + seq++; + } + + /* 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. + */ + tcp_senddata_conn ( conn, ( start_seq != seq ) ); + + /* If this packet was the last we expect to receive, set up + * timer to expire and cause the connection to be freed. + */ + 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 + * @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. */ -int tcp_bind ( struct tcp_connection *conn, uint16_t local_port ) { +static int tcp_bind ( struct tcp_connection *conn, uint16_t local_port ) { struct tcp_connection *existing; - - 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 - * - * @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. - */ -int tcp_listen ( struct tcp_connection *conn, uint16_t local_port ) { static uint16_t try_port = 1024; - int rc; #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. - */ try_port = random(); /* If no port specified, find the first available port */ @@ -600,404 +731,117 @@ int tcp_listen ( struct tcp_connection *conn, uint16_t local_port ) { for ( ; try_port ; try_port++ ) { if ( try_port < 1024 ) continue; - if ( tcp_listen ( conn, htons ( try_port ) ) == 0 ) + if ( tcp_bind ( conn, htons ( try_port ) ) == 0 ) return 0; } + DBG ( "TCP %p could not bind: no free ports remaining\n", + conn ); return -EADDRINUSE; } /* Attempt bind to local port */ - if ( ( rc = tcp_bind ( conn, local_port ) ) != 0 ) - return rc; - - /* Add to TCP connection list */ - list_add ( &conn->list, &tcp_conns ); - DBG ( "TCP opened %p on port %d\n", conn, ntohs ( 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->local_port = local_port; + DBG ( "TCP %p bound to port %d\n", conn, ntohs ( local_port ) ); return 0; } /** - * Send data + * Connect to a remote server * - * @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 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_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; - } - - /* 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 - * - * @v conn TCP connection - * @v data Data to be sent - * @v len Length of the data - * - * This function sends data to the peer socket address - */ -int tcp_send ( struct tcp_connection *conn, const void *data, size_t len ) { - struct sockaddr_tcpip *peer = &conn->peer; - struct pk_buff *pkb; - int slen; - - /* Take ownership of the TX buffer from the connection */ - pkb = conn->tx_pkb; - conn->tx_pkb = NULL; - - /* Determine the amount of data to be sent */ - slen = len < conn->snd_win ? len : conn->snd_win; - /* Copy payload */ - memmove ( pkb_put ( pkb, slen ), data, slen ); - - /* Fill up the TCP header */ - struct tcp_header *tcphdr = pkb_push ( pkb, sizeof ( *tcphdr ) ); - - /* Source port, assumed to be in network byte order in conn */ - tcphdr->src = conn->local_port; - /* Destination port, assumed to be in network byte order in peer */ - 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 ); - } - - /* Transmit packet */ - return tcpip_tx ( pkb, &tcp_protocol, peer ); -} - -/** - * Process received packet - * - * @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 ) { +int tcp_connect ( struct tcp_application *app, struct sockaddr_tcpip *peer, + uint16_t local_port ) { struct tcp_connection *conn; - struct tcp_header *tcphdr; - int32_t acked, toack; - unsigned int hlen; int rc; - /* Sanity check */ - if ( pkb_len ( pkb ) < sizeof ( *tcphdr ) ) { - DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) ); - rc = -EINVAL; - goto done; + /* 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; } - /* 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 ); - rc = -EINVAL; - goto done; - } - /* TODO: Parse TCP options */ - if ( hlen != sizeof ( *tcphdr ) ) { - DBG ( "Ignoring TCP options\n" ); + /* 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; } - /* TODO: Verify checksum */ - - /* Demux TCP connection */ - list_for_each_entry ( conn, &tcp_conns, list ) { - if ( tcphdr->dest == conn->local_port ) { - goto found_conn; - } - } - - DBG ( "No connection found on port %d\n", ntohs ( tcphdr->dest ) ); - rc = 0; - goto done; - - found_conn: - /* Stop the timer */ - stop_timer ( &conn->timer ); - - /* 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; + /* 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; } - /** - * Any packet reaching this point either contains new data or - * acknowledges previously transmitted data. + /* 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 */ - assert ( ( tcphdr->flags & TCP_ACK ) || - pkb_len ( pkb ) > sizeof ( *tcphdr ) ); - - /** - * Check if the received packet ACKs sent data - */ - 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; - } - /* Invoke the acked() callback */ - conn->snd_una += acked; - if ( conn->tcp_op->acked ) - conn->tcp_op->acked ( conn, acked ); + if ( ! ( conn->tcp_state & TCP_STATE_RCVD ( TCP_SYN ) ) ) { + conn->tcp_state = TCP_CLOSED; + tcp_dump_state ( conn ); + free_tcp ( conn ); + return; } - - /** - * Check if packet contains new data + + /* 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. */ - 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 ); - } - conn->tcp_flags |= TCP_ACK; - } - - /** - * Send data - */ - tcp_senddata ( conn ); - rc = 0; - goto done; + if ( conn->tcp_state & TCP_STATE_SENDING ( TCP_SYN ) ) + tcp_rx_ack ( conn, ( conn->snd_seq + 1 ), 0 ); - 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 ); - } - 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; + /* Send a FIN to initiate the close */ + conn->tcp_state |= TCP_STATE_SENDING ( TCP_FIN ); + tcp_dump_state ( conn ); + tcp_senddata_conn ( conn, 0 ); } /** TCP protocol */ @@ -1007,5 +851,3 @@ struct tcpip_protocol tcp_protocol __tcpip_protocol = { .tcpip_proto = IP_TCP, .csum_offset = 16, }; - -#endif /* USE_UIP */ diff --git a/src/net/tcp/ftp.c b/src/net/tcp/ftp.c index 6082d97d..1bdf8135 100644 --- a/src/net/tcp/ftp.c +++ b/src/net/tcp/ftp.c @@ -58,49 +58,31 @@ static inline const void * ftp_string_data ( struct ftp_request *ftp, } /** - * Get FTP request from control TCP connection + * Get FTP request from control TCP application * - * @v conn TCP connection + * @v app TCP application * @ret ftp FTP request */ -static inline struct ftp_request * tcp_to_ftp ( struct tcp_connection *conn ) { - return container_of ( conn, struct ftp_request, tcp ); -} - -/** - * Set overall FTP operation status - * - * @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; -} - -/** - * Clear overall FTP operation status - * - * @v ftp FTP request - */ -static void ftp_clear_status ( struct ftp_request *ftp ) { - ftp->rc = 0; +static inline struct ftp_request * tcp_to_ftp ( struct tcp_application *app ) { + return container_of ( app, struct ftp_request, tcp ); } /** * Mark FTP operation as complete * * @v ftp FTP request + * @v rc Return status code */ -static void ftp_done ( struct ftp_request *ftp ) { +static void ftp_done ( struct ftp_request *ftp, int rc ) { - DBG ( "FTP %p completed with status %d\n", ftp, ftp->rc ); + DBG ( "FTP %p completed with status %d\n", ftp, rc ); - async_done ( &ftp->aop, ftp->rc ); + /* Close both TCP connections */ + tcp_close ( &ftp->tcp ); + tcp_close ( &ftp->tcp_data ); + + /* 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; + union { + struct sockaddr_in sin; + struct sockaddr_tcpip st; + } sa; + int rc; - 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 ); + sa.sin.sin_family = AF_INET; + ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_addr, + sizeof ( sa.sin.sin_addr ) ); + ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_port, + sizeof ( sa.sin.sin_port ) ); + if ( ( rc = tcp_connect ( &ftp->tcp_data, &sa.st, 0 ) ) != 0 ){ + DBG ( "FTP %p could not create data connection\n", + ftp ); + ftp_done ( ftp, rc ); + return; + } } /* Move to next state */ @@ -176,26 +168,21 @@ static void ftp_reply ( struct ftp_request *ftp ) { } return; - - err: - /* Flag protocol error and close connections */ - ftp_set_status ( ftp, -EPROTO ); - tcp_close ( &ftp->tcp ); } /** * Handle new data arriving on FTP control channel * - * @v conn TCP connection + * @v app TCP application * @v data New data * @v len Length of new data * * Data is collected until a complete line is received, at which point * its information is passed to ftp_reply(). */ -static void ftp_newdata ( struct tcp_connection *conn, +static void ftp_newdata ( struct tcp_application *app, void *data, size_t len ) { - struct ftp_request *ftp = tcp_to_ftp ( conn ); + struct ftp_request *ftp = tcp_to_ftp ( app ); char *recvbuf = ftp->recvbuf; size_t recvsize = ftp->recvsize; char c; @@ -242,10 +229,10 @@ static void ftp_newdata ( struct tcp_connection *conn, /** * Handle acknowledgement of data sent on FTP control channel * - * @v conn TCP connection + * @v app TCP application */ -static void ftp_acked ( struct tcp_connection *conn, size_t len ) { - struct ftp_request *ftp = tcp_to_ftp ( conn ); +static void ftp_acked ( struct tcp_application *app, size_t len ) { + struct ftp_request *ftp = tcp_to_ftp ( app ); /* Mark off ACKed portion of the currently-transmitted data */ ftp->already_sent += len; @@ -254,13 +241,13 @@ static void ftp_acked ( struct tcp_connection *conn, size_t len ) { /** * Construct data to send on FTP control channel * - * @v conn TCP connection + * @v app TCP application * @v buf Temporary data buffer * @v len Length of temporary data buffer */ -static void ftp_senddata ( struct tcp_connection *conn, +static void ftp_senddata ( struct tcp_application *app, void *buf, size_t len ) { - struct ftp_request *ftp = tcp_to_ftp ( conn ); + struct ftp_request *ftp = tcp_to_ftp ( app ); const struct ftp_string *string; /* Send the as-yet-unACKed portion of the string for the @@ -269,31 +256,24 @@ static void ftp_senddata ( struct tcp_connection *conn, string = &ftp_strings[ftp->state]; len = snprintf ( buf, len, string->format, ftp_string_data ( ftp, string->data_offset ) ); - tcp_send ( conn, buf + ftp->already_sent, len - ftp->already_sent ); + tcp_send ( app, buf + ftp->already_sent, len - ftp->already_sent ); } /** * Handle control channel being closed * - * @v conn TCP connection + * @v app TCP application * * When the control channel is closed, the data channel must also be * closed, if it is currently open. */ -static void ftp_closed ( struct tcp_connection *conn, int status ) { - struct ftp_request *ftp = tcp_to_ftp ( conn ); +static void ftp_closed ( struct tcp_application *app, int status ) { + struct ftp_request *ftp = tcp_to_ftp ( app ); DBG ( "FTP %p control connection closed (status %d)\n", ftp, status ); - /* Close data channel and record status */ - ftp_set_status ( ftp, status ); - tcp_close ( &ftp->tcp_data ); - - /* Mark FTP operation as complete if we are the last - * connection to close - */ - if ( tcp_closed ( &ftp->tcp_data ) ) - ftp_done ( ftp ); + /* Complete FTP operation */ + ftp_done ( ftp, status ); } /** FTP control channel operations */ @@ -311,20 +291,20 @@ static struct tcp_operations ftp_tcp_operations = { */ /** - * Get FTP request from data TCP connection + * Get FTP request from data TCP application * - * @v conn TCP connection + * @v app TCP application * @ret ftp FTP request */ static inline struct ftp_request * -tcp_to_ftp_data ( struct tcp_connection *conn ) { - return container_of ( conn, struct ftp_request, tcp_data ); +tcp_to_ftp_data ( struct tcp_application *app ) { + return container_of ( app, struct ftp_request, tcp_data ); } /** * Handle data channel being closed * - * @v conn TCP connection + * @v app TCP application * * When the data channel is closed, the control channel should be left * alone; the server will send a completion message via the control @@ -332,36 +312,28 @@ tcp_to_ftp_data ( struct tcp_connection *conn ) { * * If the data channel is closed due to an error, we abort the request. */ -static void ftp_data_closed ( struct tcp_connection *conn, int status ) { - struct ftp_request *ftp = tcp_to_ftp_data ( conn ); +static void ftp_data_closed ( struct tcp_application *app, int status ) { + struct ftp_request *ftp = tcp_to_ftp_data ( app ); DBG ( "FTP %p data connection closed (status %d)\n", ftp, status ); /* If there was an error, close control channel and record status */ - if ( status ) { - ftp_set_status ( ftp, status ); - tcp_close ( &ftp->tcp ); - } - - /* Mark FTP operation as complete if we are the last - * connection to close - */ - if ( tcp_closed ( &ftp->tcp ) ) - ftp_done ( ftp ); + if ( status ) + ftp_done ( ftp, status ); } /** * Handle new data arriving on the FTP data channel * - * @v conn TCP connection + * @v app TCP application * @v data New data * @v len Length of new data * * Data is handed off to the callback registered in the FTP request. */ -static void ftp_data_newdata ( struct tcp_connection *conn, +static void ftp_data_newdata ( struct tcp_application *app, void *data, size_t len ) { - struct ftp_request *ftp = tcp_to_ftp_data ( conn ); + struct ftp_request *ftp = tcp_to_ftp_data ( app ); ftp->callback ( data, len ); } @@ -384,14 +356,16 @@ static struct tcp_operations ftp_data_tcp_operations = { * @v ftp FTP request */ struct async_operation * ftp_get ( struct ftp_request *ftp ) { - + int rc; + DBG ( "FTP %p fetching %s\n", ftp, ftp->filename ); ftp->tcp.tcp_op = &ftp_tcp_operations; ftp->tcp_data.tcp_op = &ftp_data_tcp_operations; ftp->recvbuf = ftp->status_text; ftp->recvsize = sizeof ( ftp->status_text ) - 1; - ftp_clear_status ( ftp ); - tcp_connect ( &ftp->tcp ); + if ( ( rc = tcp_connect ( &ftp->tcp, &ftp->server, 0 ) ) != 0 ) + ftp_done ( ftp, rc ); + return &ftp->aop; } diff --git a/src/net/tcp/hello.c b/src/net/tcp/hello.c index d74e52f9..4de7e872 100644 --- a/src/net/tcp/hello.c +++ b/src/net/tcp/hello.c @@ -10,9 +10,9 @@ * "Hello world" TCP protocol * * This file implements a trivial TCP-based protocol. It connects to - * the server specified in hello_request::tcp and transmits a single - * message (hello_request::message). Any data received from the - * server will be passed to the callback function, + * the server specified in hello_request::server and transmits a + * single message (hello_request::message). Any data received from + * the server will be passed to the callback function, * hello_request::callback(), and once the connection has been closed, * the asynchronous operation associated with the request will be * marked as complete. @@ -26,13 +26,13 @@ * } * * struct hello_request hello = { + * .server = { + * ... + * }, * .message = "hello world!", * .callback = my_callback, * }; * - * hello.sin.sin_addr.s_addr = ... server IP address ... - * hello.sin.sin_port = ... server port ... - * * rc = async_wait ( say_hello ( &hello ) ); * * @endcode @@ -44,25 +44,25 @@ */ static inline struct hello_request * -tcp_to_hello ( struct tcp_connection *conn ) { - return container_of ( conn, struct hello_request, tcp ); +tcp_to_hello ( struct tcp_application *app ) { + return container_of ( app, struct hello_request, tcp ); } -static void hello_closed ( struct tcp_connection *conn, int status ) { - struct hello_request *hello = tcp_to_hello ( conn ); +static void hello_closed ( struct tcp_application *app, int status ) { + struct hello_request *hello = tcp_to_hello ( app ); async_done ( &hello->aop, status ); } -static void hello_connected ( struct tcp_connection *conn ) { - struct hello_request *hello = tcp_to_hello ( conn ); +static void hello_connected ( struct tcp_application *app ) { + struct hello_request *hello = tcp_to_hello ( app ); hello->remaining = strlen ( hello->message ); hello->state = HELLO_SENDING_MESSAGE; } -static void hello_acked ( struct tcp_connection *conn, size_t len ) { - struct hello_request *hello = tcp_to_hello ( conn ); +static void hello_acked ( struct tcp_application *app, size_t len ) { + struct hello_request *hello = tcp_to_hello ( app ); hello->message += len; hello->remaining -= len; @@ -84,18 +84,18 @@ static void hello_acked ( struct tcp_connection *conn, size_t len ) { } } -static void hello_newdata ( struct tcp_connection *conn, void *data, +static void hello_newdata ( struct tcp_application *app, void *data, size_t len ) { - struct hello_request *hello = tcp_to_hello ( conn ); + struct hello_request *hello = tcp_to_hello ( app ); hello->callback ( data, len ); } -static void hello_senddata ( struct tcp_connection *conn, +static void hello_senddata ( struct tcp_application *app, void *buf __unused, size_t len __unused ) { - struct hello_request *hello = tcp_to_hello ( conn ); + struct hello_request *hello = tcp_to_hello ( app ); - tcp_send ( conn, hello->message, hello->remaining ); + tcp_send ( app, hello->message, hello->remaining ); } static struct tcp_operations hello_tcp_operations = { @@ -112,7 +112,11 @@ static struct tcp_operations hello_tcp_operations = { * @v hello "Hello world" request */ struct async_operation * say_hello ( struct hello_request *hello ) { + int rc; + hello->tcp.tcp_op = &hello_tcp_operations; - tcp_connect ( &hello->tcp ); + if ( ( rc = tcp_connect ( &hello->tcp, &hello->server, 0 ) ) != 0 ) + async_done ( &hello->aop, rc ); + return &hello->aop; } diff --git a/src/net/tcp/http.c b/src/net/tcp/http.c index 01f0aeac..3bba8b33 100644 --- a/src/net/tcp/http.c +++ b/src/net/tcp/http.c @@ -39,28 +39,28 @@ */ static inline struct http_request * -tcp_to_http ( struct tcp_connection *conn ) { - return container_of ( conn, struct http_request, tcp ); +tcp_to_http ( struct tcp_application *app ) { + return container_of ( app, struct http_request, tcp ); } /** * Close an HTTP connection * - * @v conn a TCP Connection + * @v app a TCP Application * @v status connection status at close */ -static void http_closed ( struct tcp_connection *conn, int status ) { - struct http_request *http = tcp_to_http ( conn ); +static void http_closed ( struct tcp_application *app, int status ) { + struct http_request *http = tcp_to_http ( app ); async_done ( &http->aop, status ); } /** * Callback after a TCP connection is established * - * @v conn a TCP Connection + * @v app a TCP Application */ -static void http_connected ( struct tcp_connection *conn ) { - struct http_request *http = tcp_to_http ( conn ); +static void http_connected ( struct tcp_application *app ) { + struct http_request *http = tcp_to_http ( app ); http->state = HTTP_REQUEST_FILE; } @@ -68,11 +68,11 @@ static void http_connected ( struct tcp_connection *conn ) { /** * Callback for when TCP data is acknowledged * - * @v conn a TCP Connection + * @v app a TCP Application * @v len the length of data acked */ -static void http_acked ( struct tcp_connection *conn, size_t len __attribute__ ((unused)) ) { - struct http_request *http = tcp_to_http ( conn ); +static void http_acked ( struct tcp_application *app, size_t len __attribute__ ((unused)) ) { + struct http_request *http = tcp_to_http ( app ); // assume that the whole GET request was sent in on epacket @@ -84,7 +84,7 @@ static void http_acked ( struct tcp_connection *conn, size_t len __attribute__ ( case HTTP_RECV_FILE: break; case HTTP_DONE: - //tcp_close(conn); + //tcp_close(app); break; default: break; @@ -95,13 +95,13 @@ static void http_acked ( struct tcp_connection *conn, size_t len __attribute__ ( /** * Callback when new TCP data is recieved * - * @v conn a TCP Connection + * @v app a TCP Application * @v data a pointer to the data recieved * @v len length of data buffer */ -static void http_newdata ( struct tcp_connection *conn, void *data, +static void http_newdata ( struct tcp_application *app, void *data, size_t len ) { - struct http_request *http = tcp_to_http ( conn ); + struct http_request *http = tcp_to_http ( app ); char *content_length; char *start = data; char *rcp; int rc; @@ -142,7 +142,7 @@ static void http_newdata ( struct tcp_connection *conn, void *data, //printf("File recv is %d\n", http->file_recv); if ( http->file_recv == http->file_size ){ http->state = HTTP_DONE; - tcp_close(conn); + tcp_close(app); } break; case HTTP_REQUEST_FILE: @@ -155,10 +155,10 @@ static void http_newdata ( struct tcp_connection *conn, void *data, /** * Callback for sending TCP data * - * @v conn a TCP Connection + * @v app a TCP Application */ -static void http_senddata ( struct tcp_connection *conn, void *buf, size_t len ) { - struct http_request *http = tcp_to_http ( conn ); +static void http_senddata ( struct tcp_application *app, void *buf, size_t len ) { + struct http_request *http = tcp_to_http ( app ); switch ( http->state ){ case HTTP_REQUEST_FILE: @@ -166,13 +166,13 @@ static void http_senddata ( struct tcp_connection *conn, void *buf, size_t len ) printf("%s\n",(char *)buf); // string is: GET HTTP/1.0\r\n\r\n - tcp_send ( conn, buf, len); + tcp_send ( app, buf, len); break; case HTTP_PARSE_HEADER: case HTTP_RECV_FILE: break; case HTTP_DONE: - //tcp_close(conn) + //tcp_close(app) break; default: break; @@ -193,8 +193,12 @@ static struct tcp_operations http_tcp_operations = { * @v http a HTTP request */ struct async_operation * get_http ( struct http_request *http ) { + int rc; + http->tcp.tcp_op = &http_tcp_operations; http->state = HTTP_REQUEST_FILE; - tcp_connect ( &http->tcp ); + if ( ( rc = tcp_connect ( &http->tcp, &http->server, 0 ) ) != 0 ) + async_done ( &http->aop, rc ); + return &http->aop; } diff --git a/src/net/tcp/iscsi.c b/src/net/tcp/iscsi.c index a0137dc5..f3e5e324 100644 --- a/src/net/tcp/iscsi.c +++ b/src/net/tcp/iscsi.c @@ -720,11 +720,14 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data, /* Check for login redirection */ if ( response->status_class == ISCSI_STATUS_REDIRECT ) { DBG ( "iSCSI %p redirecting to new server\n", iscsi ); - /* Close the TCP connection; our TCP closed() method - * will take care of the reconnection once this - * connection has been cleanly terminated. - */ tcp_close ( &iscsi->tcp ); + iscsi->status = 0; + if ( ( rc = tcp_connect ( &iscsi->tcp, &iscsi->target, + 0 ) ) != 0 ) { + DBG ( "iSCSI %p could not open TCP connection\n", + iscsi ); + iscsi_done ( iscsi, rc ); + } return; } @@ -778,8 +781,8 @@ static void iscsi_rx_login_response ( struct iscsi_session *iscsi, void *data, */ static inline struct iscsi_session * -tcp_to_iscsi ( struct tcp_connection *conn ) { - return container_of ( conn, struct iscsi_session, tcp ); +tcp_to_iscsi ( struct tcp_application *app ) { + return container_of ( app, struct iscsi_session, tcp ); } /** @@ -859,8 +862,8 @@ static void iscsi_tx_done ( struct iscsi_session *iscsi ) { * Updates iscsi->tx_offset and, if applicable, transitions to the * next TX state. */ -static void iscsi_acked ( struct tcp_connection *conn, size_t len ) { - struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); +static void iscsi_acked ( struct tcp_application *app, size_t len ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( app ); struct iscsi_bhs_common *common = &iscsi->tx_bhs.common; enum iscsi_tx_state next_state; @@ -916,9 +919,9 @@ static void iscsi_acked ( struct tcp_connection *conn, size_t len ) { * * Constructs data to be sent for the current TX state */ -static void iscsi_senddata ( struct tcp_connection *conn, +static void iscsi_senddata ( struct tcp_application *app, void *buf, size_t len ) { - struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + struct iscsi_session *iscsi = tcp_to_iscsi ( app ); struct iscsi_bhs_common *common = &iscsi->tx_bhs.common; static const char pad[] = { '\0', '\0', '\0' }; @@ -927,7 +930,7 @@ static void iscsi_senddata ( struct tcp_connection *conn, /* Nothing to send */ break; case ISCSI_TX_BHS: - tcp_send ( conn, &iscsi->tx_bhs.bytes[iscsi->tx_offset], + tcp_send ( app, &iscsi->tx_bhs.bytes[iscsi->tx_offset], ( sizeof ( iscsi->tx_bhs ) - iscsi->tx_offset ) ); break; case ISCSI_TX_AHS: @@ -938,7 +941,7 @@ static void iscsi_senddata ( struct tcp_connection *conn, iscsi_tx_data ( iscsi, buf, len ); break; case ISCSI_TX_DATA_PADDING: - tcp_send ( conn, pad, ( ISCSI_DATA_PAD_LEN ( common->lengths ) + tcp_send ( app, pad, ( ISCSI_DATA_PAD_LEN ( common->lengths ) - iscsi->tx_offset ) ); break; default: @@ -1029,7 +1032,7 @@ static void iscsi_rx_bhs ( struct iscsi_session *iscsi, void *data, /** * Receive new data * - * @v tcp TCP connection + * @v tcp TCP application * @v data Received data * @v len Length of received data * @@ -1040,9 +1043,9 @@ static void iscsi_rx_bhs ( struct iscsi_session *iscsi, void *data, * always has a full copy of the BHS available, even for portions of * the data in different packets to the BHS. */ -static void iscsi_newdata ( struct tcp_connection *conn, void *data, +static void iscsi_newdata ( struct tcp_application *app, void *data, size_t len ) { - struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); + struct iscsi_session *iscsi = tcp_to_iscsi ( app ); struct iscsi_bhs_common *common = &iscsi->rx_bhs.common; void ( *process ) ( struct iscsi_session *iscsi, void *data, size_t len, size_t remaining ); @@ -1098,38 +1101,28 @@ static void iscsi_newdata ( struct tcp_connection *conn, void *data, } } -#warning "Remove me soon" -static struct tcp_operations iscsi_tcp_operations; - /** * Handle TCP connection closure * - * @v conn TCP connection + * @v app TCP application * @v status Error code, if any * */ -static void iscsi_closed ( struct tcp_connection *conn, int status ) { - struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); - int session_status = iscsi->status; +static void iscsi_closed ( struct tcp_application *app, int status ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( app ); + int rc; /* Clear session status */ iscsi->status = 0; - /* If we are deliberately closing down, exit cleanly */ - if ( session_status & ISCSI_STATUS_CLOSING ) { - iscsi_done ( iscsi, status ); - return; - } - /* Retry connection if within the retry limit, otherwise fail */ if ( ++iscsi->retry_count <= ISCSI_MAX_RETRIES ) { DBG ( "iSCSI %p retrying connection\n", iscsi ); - /* Re-copy address to handle redirection */ - memset ( &iscsi->tcp, 0, sizeof ( iscsi->tcp ) ); - iscsi->tcp.tcp_op = &iscsi_tcp_operations; - memcpy ( &iscsi->tcp.peer, &iscsi->target, - sizeof ( iscsi->tcp.peer ) ); - tcp_connect ( conn ); + if ( ( rc = tcp_connect ( app, &iscsi->target, 0 ) ) != 0 ) { + DBG ( "iSCSI %p could not open TCP connection\n", + iscsi ); + iscsi_done ( iscsi, rc ); + } } else { printf ( "iSCSI %p retry count exceeded\n", iscsi ); iscsi_done ( iscsi, status ); @@ -1139,11 +1132,11 @@ static void iscsi_closed ( struct tcp_connection *conn, int status ) { /** * Handle TCP connection opening * - * @v conn TCP connection + * @v app TCP application * */ -static void iscsi_connected ( struct tcp_connection *conn ) { - struct iscsi_session *iscsi = tcp_to_iscsi ( conn ); +static void iscsi_connected ( struct tcp_application *app ) { + struct iscsi_session *iscsi = tcp_to_iscsi ( app ); /* Set connected flag and reset retry count */ iscsi->status = ( ISCSI_STATUS_SECURITY_NEGOTIATION_PHASE | @@ -1179,6 +1172,8 @@ static struct tcp_operations iscsi_tcp_operations = { */ struct async_operation * iscsi_issue ( struct iscsi_session *iscsi, struct scsi_command *command ) { + int rc; + assert ( iscsi->command == NULL ); iscsi->command = command; @@ -1198,9 +1193,12 @@ struct async_operation * iscsi_issue ( struct iscsi_session *iscsi, } else { /* Session not open: initiate login */ iscsi->tcp.tcp_op = &iscsi_tcp_operations; - memcpy ( &iscsi->tcp.peer, &iscsi->target, - sizeof ( iscsi->tcp.peer ) ); - tcp_connect ( &iscsi->tcp ); + if ( ( rc = tcp_connect ( &iscsi->tcp, &iscsi->target, + 0 ) ) != 0 ) { + DBG ( "iSCSI %p could not open TCP connection\n", + iscsi ); + iscsi_done ( iscsi, rc ); + } } return &iscsi->aop; @@ -1212,10 +1210,7 @@ struct async_operation * iscsi_issue ( struct iscsi_session *iscsi, * @v iscsi iSCSI session * @ret aop Asynchronous operation */ -struct async_operation * iscsi_shutdown ( struct iscsi_session *iscsi ) { - if ( iscsi->status ) { - iscsi->status |= ISCSI_STATUS_CLOSING; - tcp_close ( &iscsi->tcp ); - } - return &iscsi->aop; +void iscsi_shutdown ( struct iscsi_session *iscsi ) { + iscsi->status = 0; + tcp_close ( &iscsi->tcp ); } diff --git a/src/tests/ftptest.c b/src/tests/ftptest.c index 6b0002fb..c4795c04 100644 --- a/src/tests/ftptest.c +++ b/src/tests/ftptest.c @@ -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; diff --git a/src/tests/hellotest.c b/src/tests/hellotest.c index 4565f13a..a873c03e 100644 --- a/src/tests/hellotest.c +++ b/src/tests/hellotest.c @@ -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; diff --git a/src/tests/httptest.c b/src/tests/httptest.c index 4b569e85..7e08f079 100644 --- a/src/tests/httptest.c +++ b/src/tests/httptest.c @@ -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;