diff --git a/src/include/gpxe/tcp.h b/src/include/gpxe/tcp.h new file mode 100644 index 00000000..c6611144 --- /dev/null +++ b/src/include/gpxe/tcp.h @@ -0,0 +1,102 @@ +#ifndef _TCP_H +#define _TCP_H + +/** @file + * + * TCP protocol + * + * This file defines the gPXE TCP API. + * + */ + +#include +#include + +struct tcp_connection; + +/** + * TCP operations + * + */ +struct tcp_operations { + /** + * Connection aborted (RST received) + * + * @v conn TCP connection + */ + void ( * aborted ) ( struct tcp_connection *conn ); + /** + * Connection timed out + * + * @v conn TCP connection + */ + void ( * timedout ) ( struct tcp_connection *conn ); + /** + * Connection aborted (FIN received) + * + * @v conn TCP connection + * + * Note that acked() and newdata() may be called after + * closed(), if the packet containing the FIN also + * acknowledged data or contained new data. + */ + void ( * closed ) ( struct tcp_connection *conn ); + /** + * Connection established (SYNACK received) + * + * @v conn TCP connection + */ + void ( * connected ) ( struct tcp_connection *conn ); + /** + * Data acknowledged + * + * @v conn TCP connection + * @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 ); + /** + * New data received + * + * @v conn TCP connection + * @v data Data + * @v len Length of data + */ + void ( * newdata ) ( struct tcp_connection *conn, + void *data, size_t len ); + /** + * Transmit data + * + * @v conn TCP connection + * + * The application should transmit whatever it currently wants + * to send using tcp_send(). If retransmissions are required, + * senddata() will be called again and the application must + * regenerate the data. The easiest way to implement this is + * to ensure that senddata() never changes the application's + * state. + */ + void ( * senddata ) ( struct tcp_connection *conn ); +}; + +/** + * 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_operations *tcp_op; +}; + +extern int tcp_connect ( struct tcp_connection *conn ); +extern void tcp_send ( struct tcp_connection *conn, const void *data, + size_t len ); +extern void tcp_close ( struct tcp_connection *conn ); +extern void init_tcpip ( void ); +extern void run_tcpip ( void ); + +#endif /* _TCP_H */ diff --git a/src/proto/tcp.c b/src/proto/tcp.c new file mode 100644 index 00000000..8c732015 --- /dev/null +++ b/src/proto/tcp.c @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include "uip/uip.h" +#include "uip/uip_arp.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. + * + */ + +/** + * Initialise TCP/IP stack + * + */ +void init_tcpip ( void ) { + uip_init(); + uip_arp_init(); +} + +#define UIP_HLEN ( 40 + UIP_LLH_LEN ) + +/** + * Transmit TCP data + * + * This is a wrapper around netdev_transmit(). It gathers up the + * packet produced by uIP, and then passes it to netdev_transmit() as + * a single buffer. + */ +static void uip_transmit ( void ) { + uip_arp_out(); + if ( uip_len > UIP_HLEN ) { + memcpy ( uip_buf + UIP_HLEN, ( void * ) uip_appdata, + uip_len - UIP_HLEN ); + } + netdev_transmit ( uip_buf, uip_len ); + uip_len = 0; +} + +/** + * Run the TCP/IP stack + * + * Call this function in a loop in order to allow TCP/IP processing to + * take place. This call takes the stack through a single iteration; + * it will typically be used in a loop such as + * + * @code + * + * struct tcp_connection *my_connection; + * ... + * tcp_connect ( my_connection ); + * while ( ! my_connection->finished ) { + * run_tcpip(); + * } + * + * @endcode + * + * where @c my_connection->finished is set by one of the connection's + * #tcp_operations methods to indicate completion. + */ +void run_tcpip ( void ) { + void *data; + size_t len; + uint16_t type; + int i; + + if ( netdev_poll ( 1, &data, &len ) ) { + /* We have data */ + memcpy ( uip_buf, data, len ); + uip_len = len; + type = ntohs ( *( ( uint16_t * ) ( uip_buf + 12 ) ) ); + if ( type == UIP_ETHTYPE_ARP ) { + uip_arp_arpin(); + } else { + uip_arp_ipin(); + uip_input(); + } + if ( uip_len > 0 ) + uip_transmit(); + } else { + for ( i = 0 ; i < UIP_CONNS ; i++ ) { + uip_periodic ( i ); + if ( uip_len > 0 ) + uip_transmit(); + } + } +} + +/** + * Open a TCP connection + * + * @v conn TCP connection + * @ret 0 Success + * @ret <0 Failure + * + * This sets up a new TCP connection to the remote host specified in + * tcp_connection::sin. The actual SYN packet will not be sent out + * until run_tcpip() is called for the first time. + * + * @todo Use linked lists instead of a static buffer, and thereby + * remove the only potential failure case, giving this function + * a void return type. + */ +int 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 ); + if ( ! uip_conn ) + return -1; + + *( ( void ** ) uip_conn->appstate ) = conn; + return 0; +} + +/** + * 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::newdata() 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 ) ); + uip_send ( ( void * ) data, 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; + + assert ( conn->tcp_op->closed != NULL ); + assert ( conn->tcp_op->connected != NULL ); + assert ( conn->tcp_op->acked != NULL ); + assert ( conn->tcp_op->newdata != NULL ); + assert ( conn->tcp_op->senddata != NULL ); + + if ( uip_aborted() && op->aborted ) /* optional method */ + op->aborted ( conn ); + if ( uip_timedout() && op->timedout ) /* optional method */ + op->timedout ( conn ); + if ( uip_closed() && op->closed ) /* optional method */ + op->closed ( conn ); + if ( uip_connected() ) + op->connected ( conn ); + if ( uip_acked() ) + op->acked ( conn, uip_conn->len ); + if ( uip_newdata() ) + op->newdata ( conn, ( void * ) uip_appdata, uip_len ); + if ( uip_rexmit() || uip_newdata() || uip_acked() || + uip_connected() || uip_poll() ) + op->senddata ( conn ); +} + +/* Present here to allow everything to link. Will go into separate + * udp.c file + */ +void uip_udp_appcall ( void ) { +} diff --git a/src/util/prototester.c b/src/util/prototester.c index b4a5bc9a..f520d668 100644 --- a/src/util/prototester.c +++ b/src/util/prototester.c @@ -8,11 +8,11 @@ #include #include #include -#include -#include #include #include +#include + typedef int irq_action_t; struct nic { @@ -68,14 +68,14 @@ static inline void free_netdevice ( struct nic *nic ) { /* Do nothing */ } -static int netdev_poll ( int retrieve, void **data, size_t *len ) { +int netdev_poll ( int retrieve, void **data, size_t *len ) { int rc = static_nic.nic_op->poll ( &static_nic, retrieve ); *data = static_nic.packet; *len = static_nic.packetlen; return rc; } -static void netdev_transmit ( const void *data, size_t len ) { +void netdev_transmit ( const void *data, size_t len ) { uint16_t type = ntohs ( *( ( uint16_t * ) ( data + 12 ) ) ); static_nic.nic_op->transmit ( &static_nic, data, type, len - ETH_HLEN, @@ -245,137 +245,6 @@ static void hijack_disable ( struct hijack_device *hijack_dev ) { close ( hijack->fd ); } -/***************************************************************************** - * - * uIP wrapper layer - * - */ - -#include "../proto/uip/uip.h" -#include "../proto/uip/uip_arp.h" - -struct tcp_connection; - -struct tcp_operations { - void ( * aborted ) ( struct tcp_connection *conn ); - void ( * timedout ) ( struct tcp_connection *conn ); - void ( * closed ) ( struct tcp_connection *conn ); - void ( * connected ) ( struct tcp_connection *conn ); - void ( * acked ) ( struct tcp_connection *conn, size_t len ); - void ( * newdata ) ( struct tcp_connection *conn, - void *data, size_t len ); - void ( * senddata ) ( struct tcp_connection *conn ); -}; - -struct tcp_connection { - struct sockaddr_in sin; - struct tcp_operations *tcp_op; -}; - -int 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 ); - if ( ! uip_conn ) - return -1; - - *( ( void ** ) uip_conn->appstate ) = conn; - return 0; -} - -void tcp_send ( struct tcp_connection *conn, const void *data, - size_t len ) { - assert ( conn = *( ( void ** ) uip_conn->appstate ) ); - uip_send ( ( void * ) data, len ); -} - -void tcp_close ( struct tcp_connection *conn ) { - assert ( conn = *( ( void ** ) uip_conn->appstate ) ); - uip_close(); -} - -void uip_tcp_appcall ( void ) { - struct tcp_connection *conn = *( ( void ** ) uip_conn->appstate ); - struct tcp_operations *op = conn->tcp_op; - - assert ( conn->tcp_op->closed != NULL ); - assert ( conn->tcp_op->connected != NULL ); - assert ( conn->tcp_op->acked != NULL ); - assert ( conn->tcp_op->newdata != NULL ); - assert ( conn->tcp_op->senddata != NULL ); - - if ( uip_aborted() && op->aborted ) /* optional method */ - op->aborted ( conn ); - if ( uip_timedout() && op->timedout ) /* optional method */ - op->timedout ( conn ); - if ( uip_closed() && op->closed ) /* optional method */ - op->closed ( conn ); - if ( uip_connected() ) - op->connected ( conn ); - if ( uip_acked() ) - op->acked ( conn, uip_conn->len ); - if ( uip_newdata() ) - op->newdata ( conn, ( void * ) uip_appdata, uip_len ); - if ( uip_rexmit() || uip_newdata() || uip_acked() || - uip_connected() || uip_poll() ) - op->senddata ( conn ); -} - -void uip_udp_appcall ( void ) { -} - -static void init_tcpip ( void ) { - uip_init(); - uip_arp_init(); -} - -#define UIP_HLEN ( 40 + UIP_LLH_LEN ) - -static void uip_transmit ( void ) { - uip_arp_out(); - if ( uip_len > UIP_HLEN ) { - memcpy ( uip_buf + UIP_HLEN, ( void * ) uip_appdata, - uip_len - UIP_HLEN ); - } - netdev_transmit ( uip_buf, uip_len ); - uip_len = 0; -} - -static void run_tcpip ( void ) { - void *data; - size_t len; - uint16_t type; - int i; - - if ( netdev_poll ( 1, &data, &len ) ) { - /* We have data */ - memcpy ( uip_buf, data, len ); - uip_len = len; - type = ntohs ( *( ( uint16_t * ) ( uip_buf + 12 ) ) ); - if ( type == ETHERTYPE_ARP ) { - uip_arp_arpin(); - } else { - uip_arp_ipin(); - uip_input(); - } - if ( uip_len > 0 ) - uip_transmit(); - } else { - for ( i = 0 ; i < UIP_CONNS ; i++ ) { - uip_periodic ( i ); - if ( uip_len > 0 ) - uip_transmit(); - } - } -} - /***************************************************************************** * * "Hello world" protocol tester