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